Hash#compact
Synopsis
hash.compact # => new hash without nil values
hash.compact! # => self or nil
Description
Hash#compact returns a new hash with all entries whose values are nil removed. The original hash is unchanged.
Hash#compact! removes nil values in place. It returns self if at least one entry was removed, or nil if nothing changed.
Watch out:
compact!returnsnilwhen no changes are made, notself. This catches many developers off guard — it’s the one gotcha with these methods.
Both methods take no arguments and accept no block.
Parameters
None.
Return Value
| Method | Return value |
|---|---|
compact | A new Hash with nil values removed |
compact! | self if changes were made, nil otherwise |
What Gets Removed
Only nil values are removed. The value false is preserved.
h = { a: false, b: nil, c: 0 }
h.compact
# => { a: false, c: 0 }
Behavior Details
A critical difference between these two methods lies in what compact! returns when no nil values exist. Unlike most bang methods that return self regardless of whether changes occurred, compact! returns nil when the hash already contains no nil values.
This breaks the common ||= pattern:
config = { a: 1, b: nil }
config.compact! || config # This works fine on first call
config = { a: 1, b: 2 }
config.compact! || config # Returns nil instead of self — surprising!
# compact! returned nil, so `|| config` kicks in and returns the original hash
# which is correct, but the behavior is inconsistent with bang method conventions
Edge Cases
Empty hash
{}.compact
# => {}
{}.compact!
# => nil
No nil values present
{ a: 1, b: 2 }.compact
# => { a: 1, b: 2 }
{ a: 1, b: 2 }.compact!
# => nil
All values are nil
{ a: nil, b: nil }.compact
# => {}
{ a: nil, b: nil }.compact!
# => {}
Default values
If a hash has a default value (set via Hash.new(0) or default=), that default is preserved and unaffected by compact:
h = Hash.new(0)
h[:missing] # => 0
h.compact # => {}
# The default value of 0 remains set on the original hash
Default proc
A hash with a default proc (set via Hash.new { |h, k| h[k] = [] }) behaves differently. After calling compact, the returned hash has no default proc — it behaves like a plain hash. The original hash’s default proc is left untouched.
h = Hash.new { |h, k| h[k] = [] }
h[:a] << 1
h[:b] << 2
h.default_proc # => #<Proc:...>
h.compact
# => {}
h.compact.default_proc # => nil — it's gone
h.default_proc # => #<Proc:...> — original unchanged
Examples
Basic usage
user = { name: "Alice", email: nil, age: 30 }
user.compact
# => { name: "Alice", age: 30 }
user
# => { name: "Alice", email: nil, age: 30 } # unchanged
Destructive in-place removal
data = { x: 10, y: nil, z: nil }
data.compact!
data
# => { x: 10 }
Chaining
{ a: nil, b: 1, c: nil, d: 2 }.compact.values
# => [1, 2]