Array#select
What does select do?
select returns a new array containing all elements for which the block evaluates to truthy. It filters an array by keeping elements that match a condition, discarding the rest.
Unlike find, which returns only the first match, select collects all matching elements. If no elements match, it returns an empty array [].
Array#select is defined directly on the Array class but behaves identically to Enumerable#select — Array includes Enumerable, and Array overrides select with an optimised implementation.
Basic Usage
With a block
[1, 2, 3, 4, 5].select { |n| n > 2 } # => [3, 4, 5]
[1, 2, 3, 4, 5].select { |n| n > 10 } # => []
With a pattern argument
You can pass a pattern object instead of a block. The method calls === on the pattern for each element:
[1, 2, 3, 4].select(Integer) # => [1, 2, 3, 4]
[1, 2, 3, 4].select(String) # => []
words = ["apple", "banana", "cherry", "apricot"]
words.select(/^a/) # => ["apple", "apricot"]
This is equivalent to:
[1, 2, 3, 4].select { |n| Integer === n } # => [1, 2, 3, 4]
Without a block
When called without a block or argument, select returns an Enumerator:
[1, 2, 3].select # => #<Enumerator: [1, 2, 3]:select>
This allows chaining with other methods:
[1, 2, 3, 4, 5].select.with_index { |n, i| i > 1 }
# => [3, 4, 5]
Filtering Numbers
By numeric condition
temperatures = [12, 15, 8, 22, 19, -3, 25]
warm_days = temperatures.select { |t| t > 18 }
warm_days # => [22, 19, 25]
Using symbolic method references
numbers = [1, 2, 3, 4, 5, 6]
even = numbers.select(&:even?)
even # => [2, 4, 6]
Chaining with map
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
squares_of_evens = numbers.select(&:even?).map { |n| n**2 }
squares_of_evens # => [4, 16, 36, 64, 100]
Finding by Attribute (Arrays of Hashes)
When an array contains hashes, use the hash key to filter:
users = [
{ name: "Alice", role: "admin", active: true },
{ name: "Bob", role: "editor", active: false },
{ name: "Carol", role: "admin", active: true },
{ name: "Dave", role: "viewer", active: true }
]
admins = users.select { |u| u[:role] == "admin" && u[:active] }
admins.map { |u| u[:name] } # => ["Alice", "Carol"]
active = users.select { |u| u[:active] }
active.map { |u| u[:name] } # => ["Alice", "Carol", "Dave"]
Filtering with multiple criteria
orders = [
{ id: 1, status: "pending", total: 100 },
{ id: 2, status: "shipped", total: 250 },
{ id: 3, status: "pending", total: 75 },
{ id: 4, status: "delivered", total: 300 }
]
high_pending = orders.select do |o|
o[:status] == "pending" && o[:total] > 80
end
high_pending # => [{ id: 1, status: "pending", total: 100 }]
find_all: The Alias
select and find_all are identical — find_all is simply an alias for select:
[1, 2, 3, 4].select { |n| n > 2 } # => [3, 4]
[1, 2, 3, 4].find_all { |n| n > 2 } # => [3, 4]
Both share the same implementation, return type, and behaviour. select is more common in Ruby code; find_all reads more naturally when emphasising “finding all” matches.
select vs reject
select and reject are opposites:
| Method | Returns elements where block is… | Equivalent to… |
|---|---|---|
select | truthy (matching) | keeping matches |
reject | falsy (non-matching) | removing matches |
numbers = [1, 2, 3, 4, 5]
numbers.select { |n| n > 3 } # => [4, 5] (keep > 3)
numbers.reject { |n| n > 3 } # => [1, 2, 3] (remove > 3)
Together, they partition a collection:
numbers = [1, 2, 3, 4, 5]
passing = numbers.select { |n| n >= 3 }
failing = numbers.reject { |n| n >= 3 }
passing # => [3, 4, 5]
failing # => [1, 2]
This is equivalent to using partition:
passing, failing = numbers.partition { |n| n >= 3 }
passing # => [3, 4, 5]
failing # => [1, 2]
Mutating with select! (Bang Variant)
The bang variant select! mutates the original array in place instead of returning a new one. If no changes are made, it returns nil.
numbers = [1, 2, 3, 4, 5]
result = numbers.select! { |n| n > 2 }
numbers # => [3, 4, 5]
result # => [3, 4, 5]
result.equal?(numbers) # => true (same object)
When select! returns nil
If the block condition matches the current array (no change needed), select! returns nil:
numbers = [3, 4, 5]
result = numbers.select! { |n| n > 2 }
result # => nil (no changes made, numbers stays [3, 4, 5])
Comparing select vs select!
original = [1, 2, 3, 4, 5]
# select returns a new array, original unchanged
filtered = original.select { |n| n > 2 }
original # => [1, 2, 3, 4, 5]
filtered # => [3, 4, 5]
# select! modifies the original
mutated = original.select! { |n| n > 2 }
original # => [3, 4, 5]
mutated # => [3, 4, 5]
mutated.equal?(original) # => true
Use select! when you want to reuse the same variable rather than allocating a new array.
Performance Considerations
select always iterates through the entire collection — it cannot short-circuit like find or any?. Time complexity is O(n) regardless of when matches are found.
# Always checks all elements, even if first one matches
[1, 2, 3, 4, 5].select { |n| n > 1 }
# Checks: 1, 2, 3, 4, 5 (all five iterations)
Memory usage
select creates a new array containing all matching elements. For very large collections with few matches, consider lazy iteration:
# Eager: builds full array first
result = (1..1_000_000).to_a.select { |n| n.prime? }
# Lazy: yields matches one at a time
result = (1..1_000_000).lazy.select { |n| n.prime? }
# then take what you need: result.first(10)
Bang variant performance
select! avoids allocating a new array, which can matter for large arrays. However, it still iterates through all elements — the performance difference is solely in memory allocation, not iteration cost.
Practical Examples
Filtering transactions
transactions = [
{ description: "Salary", amount: 3000 },
{ description: "Rent", amount: -1000 },
{ description: "Groceries", amount: -150 },
{ description: "Bonus", amount: 500 }
]
income = transactions.select { |t| t[:amount] > 0 }.sum { |t| t[:amount] }
expenses = transactions.reject { |t| t[:amount] > 0 }.sum { |t| t[:amount].abs }
income # => 3500
expenses # => 1150
Combining with group_by
scores = [45, 78, 92, 55, 88, 33, 67]
passed = scores.select { |s| s >= 60 }
failed = scores.reject { |s| s >= 60 }
passed # => [78, 92, 88, 67]
failed # => [45, 55, 33]
With map for transformation
products = [
{ name: "Widget", price: 100, in_stock: true },
{ name: "Gadget", price: 200, in_stock: false },
{ name: "Doodad", price: 50, in_stock: true }
]
available_prices = products
.select { |p| p[:in_stock] }
.map { |p| p[:price] }
available_prices # => [100, 50]
See Also
- /reference/enumerable/enumerable-reject/ — Returns elements for which the block evaluates to false (opposite of select)
- /reference/enumerable/enumerable-find/ — Returns only the first element that satisfies the condition
- /reference/enumerable/enumerable-partition/ — Splits a collection into two arrays based on a condition
- /reference/enumerable/enumerable-filter-map/ — Filters and transforms elements in a single pass