Array#filter_map: Filter and Map in One Pass
arr.filter_map { |item| block } → new_array Array#filter_map runs a block over every element and returns a new array containing the block’s truthy results. The method is just Enumerable#filter_map made available on Array (since Array includes Enumerable), and it was added in Ruby 2.7. If you keep reaching for select { ... }.map { ... } or map { ... }.compact, this method is the cleaner one-pass replacement.
How filter_map works
The signature is straightforward. Pass a block and get a new array back. Without a block, you get an Enumerator you can chain on:
[1, 2, 3].filter_map { |n| n * 2 if n.odd? }
# => [2, 6]
The block’s return value is what gets tested. If the value is truthy, it goes into the result. If it is false or nil, it gets dropped. That is the whole rule, and it catches people out: false is filtered, not just nil.
[1, 0, 2, 0, 3].filter_map { |n| n }
# => [1, 0, 2, 0, 3]
0 is truthy in Ruby, so it stays. The same goes for "", [], and {}. If you want to drop those, the block has to return nil (or false) explicitly. The filter is “truthy”, not “non-empty” or “non-zero”.
Without a block, filter_map returns an Enumerator, which is useful when you want to compose it with other enumerator methods. The next block shows that, pairing filter_map with with_index to keep only elements at even positions:
[1, 2, 3, 4].filter_map
# => #<Enumerator: [1, 2, 3, 4]:filter_map>
[1, 2, 3, 4].filter_map.with_index { |n, i| n * 10 if i.even? }
# => [10, 30]
filter_map examples
The idiomatic shape is value if condition. Return the value you want to keep, or nil to skip it. The next block shows that on a list of user hashes, pulling out the email for active users and dropping everyone else in a single pass:
users = [
{ name: "Ada", active: true, email: "ada@example.com" },
{ name: "Bo", active: false, email: "bo@example.com" },
{ name: "Cleo", active: true, email: "cleo@example.com" }
]
users.filter_map { |u| u[:email] if u[:active] }
# => ["ada@example.com", "cleo@example.com"]
This works well for “extract a field but only for matching records” jobs, which is one of the most common uses in real codebases. Both the filter and the transform happen in the same block, so the array gets walked once instead of twice.
You can also use it to parse a list where some entries might fail. rescue nil inside the block turns the failure into a nil result, and the filter step then drops it. The next example parses a list of strings as base-10 integers and silently skips anything that is not a valid number:
raw = ["12", "abc", "7", "", "99"]
raw.filter_map { |s| Integer(s, 10) rescue nil }
# => [12, 7, 99]
Integer("", 10) raises ArgumentError, so the empty string gets swallowed by rescue nil and dropped by the filter step. Be specific about which exception you rescue in production code, though. A bare rescue nil catches StandardError by default, which is wider than you probably want for a parse step. rescue ArgumentError is usually the right scope.
filter_map vs select+map vs map+compact
These forms are observationally identical:
[1, 2, nil, 3, nil, 4].filter_map { |n| n && n * 10 }
# => [10, 20, 30, 40]
[1, 2, nil, 3, nil, 4]
.map { |n| n && n * 10 }
.compact
# => [10, 20, 30, 40]
[1, 2, nil, 3, nil, 4]
.select { |n| n }
.map { |n| n * 10 }
# => [10, 20, 30, 40]
The cost differs. select { ... }.map { ... } walks the array twice. map { ... }.compact walks it once but still allocates the intermediate array full of nil placeholders — see Array#select and Array#compact for the building blocks. filter_map does both in one pass, and RuboCop’s Performance/FilterMap cop will flag the other two forms for you.
If your block does not filter at all, with every return kept, prefer plain map. The per-iteration truthy check is a small tax for no benefit.
Common gotchas
falseis dropped, not justnil. If your block legitimately returnsfalseas a kept value,filter_mapwill silently throw it away. Useselect { ... }.map { ... }(ormap { ... }.compact) instead.- Truthy “empty” values are kept.
0,"",[],{}, and0.0are all truthy in Ruby, sofilter_mapwill not drop them. - No
filter_map!. There is no mutating form onArray. If you need to mutate in place, usereject!orcompact!. - Do not use it for pure transforms. If nothing is ever skipped,
mapis the clearer call. Hash#filter_mapexists too, but it returns aHash, not anArray. The block receives|key, value|and the result has to be a 2-element array, or it gets dropped. Different method, same name.
See Also
Array#map: pure transformation, no filteringArray#select: the “filter” half of the old two-pass patternEnumerable#filter_map: the parent method this one inheritsArray#compact: whatfilter_mapreplaces when paired withmapHash#filter_map: same name, different return type