Array#reject
arr.reject { |element| block } -> new_array Array#reject does the opposite of select. It filters out elements from a collection based on a condition you define in a block, returning a new result containing only the elements for which the block evaluates to falsy.
This is useful when you want to remove unwanted elements from a collection. Unlike delete, it works based on a condition rather than a specific value.
That makes reject a good fit when the filter rule is easier to describe as “drop these items” instead of “remove this exact value.” It is common in data cleanup, validation, and any place where the decision depends on the element’s properties.
Syntax
array.reject { |element| block }
array.reject # without block returns an Enumerator
When you omit the block, reject returns an Enumerator, allowing you to chain other methods or use it with various iteration patterns.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
block | Proc | Required | A block that receives each element. Return true to exclude the element, falsy to keep it. |
Return Value
Returns a new Array containing all elements for which the block returned a falsy value (nil or false). The original array remains unchanged. Returns an empty array if all elements are rejected.
Examples
Basic filtering
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Remove even numbers (keep odds)
odds = numbers.reject { |n| n.even? }
# => [1, 3, 5, 7, 9]
# Remove numbers greater than 5
small = numbers.reject { |n| n > 5 }
# => [1, 2, 3, 4, 5]
Numeric filtering is the most common use case for reject. The block receives each element and should return a truthy value for items to drop. When the predicate is a built-in method like even? or a simple comparison, the intent stays clear and the code reads almost like plain English.
Working with strings
words = ["apple", "banana", "cherry", "date", "elderberry"]
# Keep only short words (less than 6 characters)
short_words = words.reject { |word| word.length >= 6 }
# => ["apple", "date"]
# Remove words containing "a"
no_a = words.reject { |word| word.include?("a") }
# => ["cherry", "elderberry"]
String collections benefit from the same reject approach. Whether you are filtering by length, content, or pattern, the block-based syntax works the same way as it does with numbers. The condition stays close to the data, so the rule is easy to read and modify later.
Using with hashes
users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 17 },
{ name: "Charlie", age: 30 },
{ name: "Diana", age: 19 }
]
# Keep only adults (age >= 18)
adults = users.reject { |user| user[:age] < 18 }
# => [{:name=>"Alice", :age=>25}, {:name=>"Charlie", :age=>30}]
When working with arrays of hashes, reject looks at each hash as a unit. The block receives each hash and returns a truthy value for those you want to exclude. This pattern is common when filtering database records, API responses, or any tabular data represented as an array of hashes.
Using without a block (Enumerator)
numbers = [1, 2, 3, 4, 5]
# Chain with other methods
result = numbers.reject.with_index { |n, i| n + i > 4 }
# => [1, 2]
The enumerator form is handy when the rejection rule depends on more than the element itself. You can add index-aware logic, pass the chain into another method, or keep the predicate close to the rest of the processing pipeline.
Edge Cases
- Empty array returns empty array
- All elements rejected returns empty array
- No elements rejected returns a copy of the original array
- Block always returns true: returns empty array
- Block always returns false: returns copy of original
[].reject { |n| true }
# => []
[1, 2, 3].reject { |n| false }
# => [1, 2, 3]
[1, 2, 3].reject { |n| true }
# => []
Those edge cases are worth keeping in mind because reject always builds a new array. The original collection stays untouched, so the method is safe to use when you want to experiment with a filter before deciding whether to switch to the in-place version.
Common Mistakes
Confusing with delete
reject removes based on a condition, not a specific value:
# Wrong - this is for delete
[1, 2, 2, 3].reject(2) # ArgumentError: wrong number of arguments
# Correct - use delete for specific values
[1, 2, 2, 3].delete(2) # => 2, returns the deleted value
Forgetting it returns a new array
A common point of confusion is that reject does not change the receiver. It always builds and returns a fresh array, leaving the original untouched. If the calling code does not capture the return value, the filtering has no visible effect.
reject doesn’t modify the original:
numbers = [1, 2, 3, 4, 5]
numbers.reject { |n| n > 2 }
# numbers is still [1, 2, 3, 4, 5]
# If you need to modify in place, use reject!
numbers.reject! { |n| n > 2 }
# numbers is now [1, 2]
The in-place version is useful when you are intentionally mutating the array, but the plain reject call is often easier to reason about in larger methods. It makes the transformation explicit and keeps the original value available if you still need it later.
Related Methods
select— keeps elements where block returns true (opposite of reject)delete— removes elements by value or conditiondetect— returns first matching element
The aliases and related methods are helpful because they point to the shape of the decision. select keeps what matches, delete removes a specific value, and detect stops at the first match. reject sits in the middle by keeping everything that fails the condition, which is often exactly what you want for cleanup code.
See Also
Array#select— keeps elements matching conditionArray#delete— removes elements from arrayArray#detect— returns first matching element