Array#reject
What does reject do?
reject returns a new array containing all elements for which the block evaluates to falsy. It filters an array by discarding elements that match a condition, keeping the rest.
Unlike find, which returns only the first match, reject collects all non-matching elements. If all elements match the condition, it returns an empty array [].
Array#reject is defined directly on the Array class but behaves identically to Enumerable#reject — Array includes Enumerable, and Array overrides reject with an optimised implementation.
Basic Usage
With a block
[1, 2, 3, 4, 5].reject { |n| n > 2 } # => [1, 2]
[1, 2, 3, 4, 5].reject { |n| n > 10 } # => [1, 2, 3, 4, 5]
Without a block
When called without a block, reject returns an Enumerator:
[1, 2, 3].reject # => #<Enumerator: [1, 2, 3]:reject>
This allows chaining with other methods:
[1, 2, 3, 4, 5].reject.with_index { |n, i| i > 2 }
# => [1, 2, 3]
Filtering Numbers
By numeric condition
temperatures = [12, 15, 8, 22, 19, -3, 25]
cool_days = temperatures.reject { |t| t > 18 }
cool_days # => [12, 15, 8, -3]
Using symbolic method references
numbers = [1, 2, 3, 4, 5, 6]
odd = numbers.reject(&:even?)
odd # => [1, 3, 5]
Chaining with map
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
cubes_of_odds = numbers.reject(&:even?).map { |n| n**3 }
cubes_of_odds # => [1, 27, 125, 343, 729]
Removing 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 }
]
inactive = users.reject { |u| u[:active] }
inactive.map { |u| u[:name] } # => ["Bob"]
non_admins = users.reject { |u| u[:role] == "admin" }
non_admins.map { |u| u[:name] } # => ["Bob", "Dave"]
Removing 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 }
]
not_pending = orders.reject { |o| o[:status] == "pending" }
not_pending # => [{ id: 2, status: "shipped", total: 250 }, { id: 4, status: "delivered", total: 300 }]
Removing nil Values
A common use case is removing nil values from an array:
data = [1, nil, 2, nil, 3, "hello", nil]
clean = data.reject(&:nil?)
clean # => [1, 2, 3, "hello"]
Using compact as an alternative
Ruby also provides compact which removes nil values:
data = [1, nil, 2, nil, 3]
data.compact # => [1, 2, 3]
data # => [1, nil, 2, nil, 3] (original unchanged)
compact only removes nil values, while reject(&:nil?) can be generalised to reject any falsy value:
data = [1, false, 2, nil, 3]
data.reject(&:nil?) # => [1, false, 2, 3] (keeps false)
data.reject(&:!) # => [1, 2, 3] (removes both false and nil)
reject vs select
reject and select 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]
small = numbers.reject { |n| n >= 3 }
large = numbers.select { |n| n >= 3 }
small # => [1, 2]
large # => [3, 4, 5]
This is equivalent to using partition:
small, large = numbers.partition { |n| n >= 3 }
small # => [1, 2]
large # => [3, 4, 5]
Mutating with reject! (Bang Variant)
The bang variant reject! 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.reject! { |n| n > 2 }
numbers # => [1, 2]
result # => [1, 2]
result.equal?(numbers) # => true (same object)
When reject! returns nil
If the block condition matches nothing (no change needed), reject! returns nil:
numbers = [1, 2, 3]
result = numbers.reject! { |n| n > 10 }
result # => nil (no changes made, numbers stays [1, 2, 3])
Comparing reject vs reject!
original = [1, 2, 3, 4, 5]
# reject returns a new array, original unchanged
filtered = original.reject { |n| n > 2 }
original # => [1, 2, 3, 4, 5]
filtered # => [1, 2]
# reject! modifies the original
mutated = original.reject! { |n| n > 2 }
original # => [1, 2]
mutated # => [1, 2]
mutated.equal?(original) # => true
Use reject! when you want to reuse the same variable rather than allocating a new array.
Performance Considerations
reject 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].reject { |n| n > 1 }
# Checks: 1, 2, 3, 4, 5 (all five iterations)
Memory usage
reject creates a new array containing all non-matching elements. For very large collections with few non-matches, consider lazy iteration:
# Eager: builds full array first
result = (1..1_000_000).to_a.reject { |n| n.odd? }
# Lazy: yields matches one at a time
result = (1..1_000_000).lazy.reject { |n| n.odd? }
# then take what you need: result.first(10)
Bang variant performance
reject! 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 records
products = [
{ name: "Widget", price: 100, discontinued: false },
{ name: "Gadget", price: 200, discontinued: true },
{ name: "Doodad", price: 50, discontinued: false },
{ name: "Thingamajig", price: 75, discontinued: true }
]
active_products = products.reject { |p| p[:discontinued] }
active_products.map { |p| p[:name] } # => ["Widget", "Doodad"]
Cleaning user input
raw_input = ["alice", "", "bob", " ", "carol", nil, "dave"]
# Remove blank and nil strings
clean = raw_input.reject { |s| s.nil? || s.strip.empty? }
clean # => ["alice", "bob", "carol", "dave"]
Removing outliers
scores = [45, 78, 92, 55, 88, 33, 67, 200, 150]
# Remove unusually high scores
reasonable = scores.reject { |s| s > 100 }
reasonable # => [45, 78, 92, 55, 88, 33, 67]
Combining with sum
transactions = [
{ description: "Salary", amount: 3000 },
{ description: "Rent", amount: -1000 },
{ description: "Groceries", amount: -150 },
{ description: "Bonus", amount: 500 }
]
total = transactions.reject { |t| t[:amount] < 0 }.sum { |t| t[:amount] }
total # => 3500
See Also
- /reference/array-methods/array-select/ — Returns elements for which the block evaluates to true (opposite of reject)
- /reference/enumerable/enumerable-reject/ — Enumerable version of reject with the same behaviour
- /reference/enumerable/enumerable-partition/ — Splits a collection into two arrays based on a condition