rubyguides

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! returns nil when no changes are made, not self. 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

MethodReturn value
compactA 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]

See Also