Hash#reject
hash.reject { |key, value| block } -> hash Hash · Updated April 3, 2026 · Hash Methods Hash#reject returns a new hash containing only the key-value pairs where the block returns falsy. It is the inverse of Hash#select — where select keeps pairs that pass the condition, reject discards them. The original hash is never modified by the non-bang version.
Syntax
hash.reject { |key, value| condition } # => new hash
hash.reject # => Enumerator
hash.reject! { |key, value| condition } # => self or nil
hash.reject! # => Enumerator
Both methods return an Enumerator when called without a block, so you can chain other iterators or pass the iterator to lazy.
Hash#reject vs Hash#reject!
The bang/non-bang distinction follows Ruby’s standard pattern:
| Method | Modifies original? | Returns when changed | Returns when nothing removed |
|---|---|---|---|
reject | No | New hash | New hash (copy) |
reject! | Yes | self | nil |
h = { a: 1, b: 2, c: 3 }
new_h = h.reject { |k, v| v > 1 }
puts h # => {:a=>1, :b=>2, :c=>3} (unchanged)
puts new_h # => {:a=>1}
h.reject! { |k, v| v > 1 }
puts h # => {:a=>1} (modified in place)
That nil return from reject! trips people up. When nothing gets removed, you get nil instead of the hash or self:
h = { a: 1, b: 2 }
result = h.reject! { |k, v| v > 10 }
result # => nil (nothing removed — returns nil, not h)
h # => {:a=>1, :b=>2} (unchanged)
result = h.reject! { |k, v| v > 0 }
result # => {} (something removed — returns self)
h # => {}
This means you can’t safely chain reject! the way you chain reject. After h.reject! { ... } you might have nil or the now-empty hash — neither is a hash you can call hash methods on predictably.
reject is NOT delete_if
Ruby documents delete_if as the equivalent of reject!, but they are separate C implementations, not aliases. They produce the same result, but delete_if always returns self, while reject! can return nil:
h = { a: 1, b: 2, c: 3 }
# delete_if: always returns self
h.dup.delete_if { |k, v| v > 2 } # => {:a=>1, :b=>2}
# reject!: returns nil if nothing removed
h.dup.reject! { |k, v| v > 10 } # => nil
h.dup.reject! { |k, v| v > 2 } # => {:a=>1, :b=>2}
Both remove entries where the block returns truthy. The only behavioral difference is that reject! signals “no changes” with nil.
reject vs select (and filter)
select and filter are identical — filter is the newer name. They keep pairs where the block returns truthy. reject is the exact inverse:
| Method | Keeps pair when block returns… | Alias |
|---|---|---|
select / filter | Truthy | Yes (filter = select) |
reject | Falsy | No |
h = { a: 1, b: 2, c: 3, d: 4 }
h.select { |k, v| v > 2 } # => {:c=>3, :d=>4}
h.reject { |k, v| v > 2 } # => {:a=>1, :b=>2}
h.filter { |k, v| v > 2 } # => {:c=>3, :d=>4} (filter == select)
reject { |k,v| condition } gives the same result as select { |k,v| !condition }. The bang variants follow the same symmetry:
h = { a: 1, b: 2, c: 3, d: 4 }
h.select! { |k, v| v > 2 } # => {:c=>3, :d=>4}
h.filter! { |k, v| v > 2 } # => {:c=>3, :d=>4} (same thing)
h.reject! { |k, v| v > 2 } # => {:a=>1, :b=>2}
select! and filter! are true aliases in the C source. reject! is its own implementation.
Enumerator Form
Without a block, both return an Enumerator:
h = { a: 1, b: 2, c: 3 }
h.reject.each { |k, v| v.even? } # => {:a=>1, :c=>3}
h.reject!.each { |k, v| v.even? } # => {:a=>1, :c=>3}
This lets you pass the Enumerator to lazy or chain other iterator methods.
Common Patterns
Cleaning nil values from user input
input = { name: "Alice", email: "alice@example.com", phone: nil, age: nil }
input.reject { |_k, v| v.nil? }
# => {:name=>"Alice", :email=>"alice@example.com"}
Removing entries by key pattern
env = { RAILS_ENV: "production", RACK_ENV: "production", DEBUG: "true", HOME: "/root" }
env.reject { |key, _| key.to_s.start_with?("RAILS") }
# => {:DEBUG=>"true", :HOME=>"/root"}
Chaining filters
data = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 }
data.select { |k, v| v > 2 }.reject { |k, v| v.even? }
# => {:c=>3, :e=>5}
Edge Cases
Empty hash: reject returns {}. reject! returns nil.
All pairs rejected: reject returns {}. reject! returns self (the now-empty hash).
Default values are not copied to the new hash:
h = Hash.new(0)
h[:missing] # => 0
h.reject { |k, v| v > 10 }
h[:missing] # => 0 (original unchanged)
The default value or proc is not copied to the returned hash by reject. The new hash has nil defaults.