Array#select

Updated April 1, 2026 · Array Methods
ruby array select filter stdlib

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:

MethodReturns elements where block is…Equivalent to…
selecttruthy (matching)keeping matches
rejectfalsy (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