rubyguides

Enumerable#filter_map

The filter_map method filters elements with a block and transforms them in a single pass. It’s equivalent to select.map but more efficient since it only iterates once.

When you would otherwise write a filter and then immediately transform the surviving values, filter_map gives you both steps in one expression, which is often easier to read.

Enumerators benefit from this style too, since a short block can express both the keep rule and the output shape. Keeping the code close to the data avoids building a temporary array you only need for a moment.

how it works

filter_map combines two operations:

  1. Filter — keep elements where the block returns truthy
  2. Map — transform the kept elements
collection.filter_map { |element| block_return }
# Equivalent to: collection.select { |x| block_return }.map { |x| block_return }
# But more efficient — single iteration

When you chain select followed by map, Ruby builds a temporary array for the filtered results and then builds another for the transformed results. With filter_map, the filtering and transformation happen in the same iteration, so there is no intermediate allocation. The examples below show how this plays out with different kinds of input data.

Basic Usage

Filter and transform in one step:

numbers = [1, 2, 3, 4, 5, 6]

# Keep only even numbers and double them
result = numbers.filter_map { |n| n.even? ? n * 2 : nil }
# => [4, 8, 12]

The block here returns n * 2 for even numbers and nil for odd ones. Because filter_map keeps truthy results and discards nil, only the doubled even values end up in the output array. This pattern appears often when you have a clear keep-or-skip rule that also needs to reshape the kept values in one step.

Remove nil values while transforming:

values = ["1", "two", "3", "four", "5"]

# Convert to integers, discarding non-numeric strings
result = values.filter_map { |s| Integer(s) rescue nil }
# => [1, 3, 5]

The Integer() call raises an error for non-numeric strings like "two", but the inline rescue nil catches the exception and returns nil instead. Since filter_map drops nil results, only the successfully parsed integers make it through. This is a compact pattern for parsing mixed input without a separate validation loop.

filtering strings

Process and filter strings:

words = ["hello", "", "world", " ", "ruby", nil]

# Keep non-empty strings, upcase them
result = words.filter_map { |w| w&.strip&.upcase if w && !w.empty? }
# => ["HELLO", "WORLD", "RUBY"]

The safe navigation operator &. handles the nil entries gracefully while the conditional clause skips blank strings. Combined with strip and upcase, you get clean, uppercase words in a single pass. This is a common cleanup pattern when working with user-submitted text where some entries may be empty, nil, or have extra whitespace.

Extract and transform data from hashes:

users = [
  { name: "Alice", email: nil },
  { name: "Bob", email: "bob@example.com" },
  { name: "Carol", email: "carol@example.com" }
]

emails = users.filter_map { |u| u[:email] }
# => ["bob@example.com", "carol@example.com"]

When the block returns a hash value directly, nil values are automatically discarded: Alice has no email, so she is excluded from the output. This makes filter_map especially convenient for extracting optional fields from structured data. You don’t need a separate compact call afterward because the method already handles the nil-skipping for you.

with conditional logic

Generate results only for elements that meet a condition:

orders = [
  { id: 1, total: 150, status: "pending" },
  { id: 2, total: 50, status: "completed" },
  { id: 3, total: 200, status: "completed" },
  { id: 4, total: 75, status: "pending" }
]

# Get IDs of completed orders over $100
order_ids = orders.filter_map { |o| o[:id] if o[:status] == "completed" && o[:total] > 100 }
# => [3]

The trailing if modifier is a concise way to express a keep-or-skip rule inside filter_map. When the condition is false, the block returns nil, and the element is dropped. This pattern reads naturally in situations where you want to extract one field from records that satisfy multiple criteria, like filtering completed orders above a certain total.

Extract nested values:

data = [
  { user: { profile: { name: "Alice" } } },
  { user: nil },
  { user: { profile: { name: "Bob" } } },
  { user: { profile: nil } }
]

names = data.filter_map { |d| d.dig(:user, :profile, :name) }
# => ["Alice", "Bob"]

dig returns nil when any intermediate key is missing, which pairs perfectly with filter_map. The two nil-producing entries (no user, no profile) are silently skipped while the valid nested names come through. This is a clean way to extract deeply nested optional data without writing a cascade of && checks or rescue blocks.

MethodBehavior
filter_mapKeeps truthy results, discards nil/false
mapReturns transformed result for every element
selectKeeps all elements, returns same type
collectAlias for map
numbers = [1, 2, 3, 4, 5]

numbers.map { |n| n * 2 }          # => [2, 4, 6, 8, 10]
numbers.select { |n| n.even? }    # => [2, 4]
numbers.filter_map { |n| n * 2 if n.even? }  # => [4, 8]

The comparison table above shows how each method handles the same input differently. map transforms every element, select keeps elements as-is, and filter_map both filters and transforms. The three-line example illustrates why you would pick one over the others: map for uniform transformation, select for filtering, and filter_map when you need both in one go.

Performance Benefits

filter_map is more efficient than chaining select and map:

# Two iterations
result = numbers.select { |n| n.even? }.map { |n| n * 2 }

# One iteration
result = numbers.filter_map { |n| n.even? ? n * 2 : nil }

The first version walks the collection twice: once to select even numbers and once to double them. The second version walks it only once, testing and transforming in a single pass. For small arrays the difference is negligible, but when you’re processing thousands of records or running expensive computations in the block, halving the iteration count can make a measurable difference in wall-clock time.

This matters for large datasets or expensive operations in the block.

When the keep-or-drop rule is simple, filter_map helps the code feel direct instead of split across two calls. If the block clearly returns a value for items you want and nil for items you do not, the intent is visible in a single expression.

practical examples

parsing and filtering data

strings = ["42", "abc", "99", "hello", "7"]

# Convert to integers, keep only positive
nums = strings.filter_map { |s| Integer(s) if Integer(s) > 0 rescue nil }
# => [42, 99, 7]

This example parses a mixed list of strings and keeps only the positive integers. The if Integer(s) > 0 check runs only after Integer(s) succeeds, and the inline rescue nil handles the non-numeric entries. Together they produce a clean list of positive numbers without intermediate arrays or separate validation steps.

extracting valid records

records = [
  { name: "Alice", age: 30, email: "alice@example.com" },
  { name: "Bob", age: nil, email: "bob@example.com" },
  { name: "Carol", age: 25, email: nil },
  { name: "Dave", age: 35, email: "dave@example.com" }
]

# Get names of people with both age and email
valid_names = records.filter_map { |r| r[:name] if r[:age] && r[:email] }
# => ["Alice", "Dave"]

Notice how the conditional if r[:age] && r[:email] is a multi-field gate: records missing either field return nil from the block and are dropped. Only Alice and Dave have both fields non-nil, so only their names appear in the output. This pattern scales well to records with optional fields where you want to process only the complete entries.

processing API responses

responses = [
  { status: 200, data: { users: [] } },
  { status: 404, data: nil },
  { status: 500, data: nil },
  { status: 200, data: { items: [] } }
]

# Extract successful response data
successful_data = responses.filter_map { |r| r[:data] if r[:status] == 200 }

This is a natural fit for API response handling where only successful responses carry usable data. The block extracts the data field only when the status is 200, silently skipping errors. The result is an array of response bodies from successful requests without any error-handling boilerplate inside the iteration.

Return Value

Always returns an array:

[].filter_map { |x| x }              # => []

[1, 2, 3].filter_map { |n| n if n.even? }
# => [2]

filter_map always returns an array, even when called on an empty collection or when no elements survive the filter. This consistency makes it safe to chain with other array methods like first, join, or each without worrying about nil. The return type never changes, which simplifies downstream code that consumes the result. When you call this method on any enumerable, the contract is always the same: an array comes back, and any element whose block result was nil is absent from that array.

edge cases

Block returns false (not nil):

[1, 2, 3].filter_map { |n| false }
# => [false]

The key subtlety of filter_map is that it keeps false but drops nil. A block that evaluates to false produces an array element, while a block that evaluates to nil produces nothing. Understanding this distinction helps when filtering boolean values or when the block might return either kind of falsy result.

Remember that false is kept, while nil is dropped. That distinction matters when you are working with boolean values or placeholders that should survive the pass.

All elements filtered out:

[1, 2, 3].filter_map { |n| nil }
# => []

When the block returns nil for every element, the result is an empty array rather than an array of nils. This is consistent with the method’s design: nil means “skip this element,” not “include a nil value.” The distinction between false-as-value and nil-as-skip is a central design choice in the method’s behavior.

Mixed nil and false:

[1, 2, 3].filter_map { |n| n == 2 ? nil : n }
# => [1, 3]

[1, 2, 3].filter_map { |n| n == 2 ? false : n }
# => [1, false, 3]

See Also