In Ruby, when do partition and filter_map beat chaining select with reject or map, and which values does filter_map drop?
answer
- one pass instead of two
- partition returns [truthy, falsy]
- filter_map keeps block results
- drops nil and false alike
- a boolean block returns trues
basics
~20 spartition splits a collection in one pass into [matching, rest]. filter_map runs a block once per element and keeps its truthy results, dropping both nil and false, so it replaces a map followed by compact or a select followed by map.
solid answer
~40 s`partition` answers "which match and which do not" in **one pass**: `overdue, current = invoices.partition(&:overdue?)` returns a two-element array, truthy group first, instead of calling `select` and then `reject` with the same test. `filter_map` combines filtering with transformation: it calls the block once per element and keeps each **truthy block result**, so `invoices.filter_map { |i| i.number if i.overdue? }` returns the overdue invoice numbers. Two traps: it drops `false` as well as `nil`, so a legitimate `false` value disappears, and a block that returns only a boolean gives you an array of `true`s rather than the elements. Both methods return arrays, including on a `Hash`.
code
ruby · 16 linesInvoice = Data.define(:number, :days_late, :disputed)
invoices = [
Invoice.new(number: "INV-7", days_late: 5, disputed: false),
Invoice.new(number: "INV-9", days_late: 45, disputed: true),
Invoice.new(number: "INV-12", days_late: 70, disputed: false)
]
overdue, current = invoices.partition { |i| i.days_late > 30 }
overdue.map(&:number) # => ["INV-9", "INV-12"]
current.map(&:number) # => ["INV-7"]
invoices.filter_map { |i| i.number if i.days_late > 30 }
# => ["INV-9", "INV-12"]
invoices.filter_map { |i| i.days_late > 30 } # => [true, true]
invoices.filter_map(&:disputed) # => [true], the falses are gone
invoices.map(&:disputed).compact # => [false, true, false]go deeper
Recall that partition returns two arrays, matches first, and filter_map keeps the block's truthy results.
Explain destructuring partition's result, why filter_map drops false as well as nil, and what a boolean-only block returns.
Replace duplicated select/reject tests and select-then-map chains in review, and keep map.compact where false is a real value.
Weigh single-pass idioms against clarity for the team, and prefer the method whose name states the intent of the split or filter.
## Two methods that save a pass Many filtering tasks in Ruby are written as two steps with the same test or a test followed by a transformation. `Enumerable` provides two methods that do these in a single pass: - **`partition`**: split into matches and non-matches. - **`filter_map`**: keep some elements and transform them at the same time. ## `partition` `partition` calls the block for every element and returns an array of **two arrays**: 1. the elements for which the block was **truthy**; 2. all the other elements. The usual idiom destructures it straight away: ```ruby overdue, current = invoices.partition { |i| i.days_late > 30 } ``` Compared with `select` plus `reject`: | | `select` + `reject` | `partition` | |---|---|---| | Passes over the data | two | one | | Block calls per element | two | one | | Risk of tests drifting apart | yes, two copies | no, one test | | Result | two separate arrays | `[truthy_array, falsy_array]` | The single test is the bigger win: when someone later changes "30 days" to "45 days", there is only one place to change. `partition` always returns two arrays, either of which may be empty, and on a `Hash` the elements are `[key, value]` pairs. `partition` splits into exactly two groups. For more than two, grouping by a key is a different method. ## `filter_map` `filter_map` calls the block once per element and returns an array of the block's **truthy results**. It is the combination of `map` and `select` in the order people usually want: ```ruby invoices.filter_map { |i| i.number if i.days_late > 30 } ``` The `if` modifier returns `nil` when the condition fails, and `filter_map` drops that `nil`. It replaces two common chains: - `invoices.select { |i| i.days_late > 30 }.map(&:number)` walks twice and builds an intermediate array. - `invoices.map { |i| i.number if i.days_late > 30 }.compact` builds an array full of `nil`s first. ## The two `filter_map` traps **It drops `false` too.** Anything falsy is removed, and in Ruby that means `nil` and `false`. If the transformation can legitimately produce `false`, for example extracting a boolean `disputed` flag, those results vanish silently. Use `map` followed by `compact`, which removes only `nil`, when `false` must survive. **It keeps block results, not elements.** A block that returns only a condition gives you the condition's values: ```ruby invoices.filter_map { |i| i.days_late > 30 } # => [true, true] ``` That is almost never intended. If the goal is the elements themselves, `select` is the right method; `filter_map` is for when you want something *derived* from each kept element. ## On a `Hash` Both methods come from `Enumerable`, so on a `Hash` they see `[key, value]` pairs and return **arrays**: - `balances.partition { |_customer, days| days > 30 }` returns two arrays of pairs. Mapping each back with `to_h`, as in `.map(&:to_h)`, restores two hashes. - `balances.filter_map { |customer, days| customer if days > 30 }` returns an array of customer names directly, which is often all the reminder job needs. This differs from `Hash`'s own filtering methods, which return a hash; knowing which methods keep the receiver's type and which fall back to `Enumerable`'s arrays avoids surprises when the result is passed on. ## Choosing among the selection methods - Need the matching **elements**: `select` (or `reject` for the rest). - Need **both** groups: `partition`. - Need a **value derived** from each matching element: `filter_map`. - Need the first match only: `find`. All four return arrays except `find`, and all four walk the whole collection except `find`. ## In an interview Explain `partition`'s return shape and destructuring, then `filter_map`'s truthy-results rule. The follow-ups interviewers like are exactly the traps: what happens to `false`, and what a boolean-returning block produces.
- When should you use map followed by compact instead of filter_map?When the transformed values can legitimately be `false`. `filter_map` drops every falsy result, so `false` disappears along with `nil`. `map { ... }.compact` removes only `nil`, keeping the `false` values, at the cost of building one intermediate array.
- What does partition return when no element matches?Still a two-element array: an empty first array and every element in the second, for example `[[], [inv1, inv2]]`. Destructuring with `overdue, current = ...` therefore always binds two arrays, so callers can iterate either without a nil check.
saying these in an interview costs you the question
- filter_map removes only nil values, like compact
- filter_map returns the original elements for which the block was truthy
- partition returns a hash keyed by true and false
- partition needs two passes, one per group
- select followed by map is the only way to filter and transform