Enumerable#partition
What does partition do?
partition splits a collection into two arrays based on a condition. The first array contains elements for which the block evaluates to truthy. The second array contains the rest.
It always returns a two-element array: [matching, non_matching]. This makes it ideal when you need both the “yes” and “no” groups from a single pass over the collection.
Key takeaways
partitionsplits a collection into two groups in a single pass.- The first return value holds the truthy matches, and the second holds everything else.
- It is a good fit when you need both results instead of keeping only one side.
- Use it when the predicate is simple and the collection is already in memory.
- Reach for
selectorrejectwhen you only need one group.
Basic usage
With a block
[1, 2, 3, 4, 5].partition { |n| n > 3 }
# => [[4, 5], [1, 2, 3]]
evens, odds = [1, 2, 3, 4, 5].partition { |n| n.even? }
evens # => [2, 4]
odds # => [1, 3, 5]
The block returns a boolean for each element, and partition collects all truthy results in the first array and all falsy results in the second. Destructuring the return value into named variables like evens, odds makes the intent readable without needing array indexing. This is the most common calling convention in everyday Ruby code.
With predicate patterns
You can pass a pattern object instead of a block. Each element is tested against the pattern using ===:
[1, 2, 3, 4].partition(Integer) # => [[1, 2, 3, 4], []]
[1, 2, 3, 4].partition(String) # => [[], [1, 2, 3, 4]]
words = ["apple", "banana", "cherry", "apricot"]
short, long = words.partition { |w| w.length <= 5 }
short # => ["apple"]
long # => ["banana", "cherry", "apricot"]
Passing a class like Integer or String invokes === for membership testing, which means you can separate elements by type without writing an explicit block. The pattern object approach is a concise option when the partition rule is simply “is this element an instance of X?” and you want to keep the call site minimal.
Without a block
When called without a block or argument, partition returns an Enumerator object:
[1, 2, 3].partition # => #<Enumerator: [1, 2, 3]:partition>
Getting an Enumerator back instead of an immediate result gives you the flexibility to compose partition with other iteration methods. You can delay the actual split until later or wrap it in a lazy pipeline. This is a standard pattern across the Ruby standard library for methods that normally expect a block.
This allows chaining with other iteration methods:
[1, 2, 3, 4, 5]
.partition.with_index { |n, i| i.even? }
# => [[1, 3, 5], [2, 4]]
Chaining with_index after partition splits elements based on their position rather than their value. The first array gets elements at even indices (0, 2, 4), the second gets odd indices (1, 3). This is a clean way to interleave or de-interleave sequences without manual index tracking.
Practical examples
Separating valid and invalid data
records = [
{ id: 1, email: "alice@example.com", confirmed: true },
{ id: 2, email: "bob@example.com", confirmed: false },
{ id: 3, email: nil, confirmed: true },
{ id: 4, email: "carol@example.com", confirmed: true }
]
valid, invalid = records.partition { |r| r[:email] && r[:confirmed] }
valid # => [{ id: 1, ... }, { id: 4, ... }]
invalid # => [{ id: 2, ... }, { id: 3, ... }]
This is a natural data-validation split: valid records go left, incomplete or unconfirmed records go right. Both groups stay accessible for further processing, so you can log the invalid entries while continuing to work with the valid ones. The pattern is common in import scripts and ETL pipelines.
Even and odd numbers
numbers = (1..10).to_a
evens, odds = numbers.partition(&:even?)
evens # => [2, 4, 6, 8, 10]
odds # => [1, 3, 5, 7, 9]
The &:even? shortcut passes the even? method as a block argument, which tests each number and splits the collection cleanly. This shorthand works with any single-argument predicate method and keeps the call site short. It is a common Ruby idiom that pairs naturally with partition because the method only needs a yes-or-no answer for each element.
Pass and fail scores
scores = { alice: 85, bob: 55, charlie: 92, diana: 48, eve: 70 }
passing, failing = scores.partition { |_, score| score >= 60 }
passing # => [[:alice, 85], [:charlie, 92], [:eve, 70]]
failing # => [[:bob, 55], [:diana, 48]]
When called on a hash, partition yields key-value pairs to the block, so each element in the returned arrays is a two-element [key, value] pair. The underscore in |_, score| discards the key, making it clear the split depends only on the score value.
Splitting by category
products = [
{ name: "Widget", price: 100, category: "electronics" },
{ name: "Screwdriver", price: 15, category: "tools" },
{ name: "Monitor", price: 250, category: "electronics" },
{ name: "Hammer", price: 30, category: "tools" },
{ name: "Keyboard", price: 80, category: "electronics" }
]
electronics, other = products.partition { |p| p[:category] == "electronics" }
electronics.count # => 3
other.count # => 2
Splitting by a field like category keeps the groups separate for independent handling. You can sum prices, count items, or render each group differently without mixing them. The block only needs to check one field, which keeps the partition rule simple even when the records carry many attributes.
Processing both groups differently
Because partition returns two arrays, you can immediately process each group:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
low, high = numbers.partition { |n| n <= 5 }
low_sum = low.sum # => 15
high_doubled = high.map { |n| n * 2 } # => [12, 14, 16, 18, 20]
After the split, each group is a plain array ready for further processing. You can sum one group and map over the other without re-filtering the original collection. This saves a second pass and keeps the grouping logic in one place.
partition vs select
| Method | Returns | Description |
|---|---|---|
select | One array | Only the matching elements |
partition | Two-element array | Both matching and non-matching |
select discards the non-matching elements. partition keeps both groups:
numbers = [1, 2, 3, 4, 5]
selected = numbers.select { |n| n > 3 }
# => [4, 5]
partitioned = numbers.partition { |n| n > 3 }
# => [[4, 5], [1, 2, 3]]
Running the same predicate with select and partition side by side shows the key difference: select throws away the non-matching half, while partition returns it alongside the matches. When you need both the yes and no groups, partition saves you from calling reject separately.
Using select and reject together is equivalent to partition, but partition is more efficient — it makes a single pass through the collection:
numbers = [1, 2, 3, 4, 5]
# Two passes:
matching = numbers.select { |n| n > 3 }
non_matching = numbers.reject { |n| n > 3 }
# One pass (equivalent result):
matching, non_matching = numbers.partition { |n| n > 3 }
The two-pass approach works but doubles the iteration cost. For large collections or expensive block logic, the single pass of partition is measurably faster. It also keeps the grouping rule in one place, so updating the condition later only requires one edit.
partition vs group_by
| Method | Returns | Key type |
|---|---|---|
partition | Two-element array | Boolean (truthy/falsy) |
group_by | Hash | Any value the block returns |
partition always produces exactly two groups based on truthiness. group_by produces as many groups as there are unique return values:
numbers = [1, 2, 3, 4, 5, 6]
partitioned = numbers.partition { |n| n.even? }
# => [[2, 4, 6], [1, 3, 5]] - always exactly 2 groups
grouped = numbers.group_by { |n| n.even? }
# => {true=>[2, 4, 6], false=>[1, 3, 5]} - hash, boolean keys
The output structure reveals the design difference: partition gives you a flat two-element array you can destructure, while group_by wraps the groups in a hash keyed by the block’s return values. For binary splits the array shape is usually more convenient because you can name the two sides directly with parallel assignment.
When you only need two groups and don’t need the hash structure, partition is cleaner:
# partition gives you direct arrays to work with
passing, failing = scores.partition { |_, s| s >= 60 }
average_pass = passing.sum { |_, s| s } / passing.size
# group_by requires extracting values from the hash
grouped = scores.group_by { |_, s| s >= 60 }
average_pass = grouped[true].sum / grouped[true].size
With partition, the two groups arrive as named variables you can use directly. With group_by, you must reach into the hash with boolean keys, which adds a layer of indirection. When the split is binary, partition produces cleaner code.
Performance considerations
partition iterates through the entire collection once. Time complexity is O(n).
[1, 2, 3, 4, 5].partition { |n| n > 2 }
# Checks: 1, 2, 3, 4, 5 (all five iterations)
Since partition always makes a full pass, it cannot short-circuit like find or any?. If you only need one of the two groups, using select or reject directly may be more appropriate.
Common mistakes
The most common mistake is expecting partition to behave like select. It does not discard the non-matching values, so you get two arrays back every time. That is useful when you need both groups, but it can surprise you if you only remember the matching side.
Another mistake is reaching for partition when the block is expensive and you only need a yes-or-no answer. In that case any?, all?, or none? is often the clearer and cheaper option.
Conclusion
partition is the right tool when you want to split a collection into two explicit groups and keep both of them. It keeps the code short, makes the intent obvious, and avoids a second pass through the data. If you only need one side, the smaller select or reject call is usually enough.
See also
- /reference/enumerable/enumerable-select/ — Returns only elements for which the block is truthy
- /reference/enumerable/enumerable-reject/ — Returns elements for which the block is falsy
- /reference/enumerable/enumerable-group-by/ — Groups elements by any criteria into a hash
- /reference/enumerable/enumerable-none/ — Checks if no elements match a condition