rubyguides

Hash#compare_by_identity

What is compare_by_identity?

By default, a Ruby Hash uses the eql? method and the hash method to determine whether two keys are the same. Keys that are equal in value—like two separate string objects containing the same text—collide and refer to the same bucket entry.

compare_by_identity switches the Hash into identity comparison mode. Instead of calling eql? and hash on keys, the Hash uses each key’s object identity (its object_id) for lookups. Two objects are considered the same key only if they are literally the same object in memory.

Available since Ruby 1.8.

This mode is useful when you need to store values keyed by object reference rather than by object value.

Signature and return value

hash.compare_by_identity -> self

Calling compare_by_identity returns self, which allows method chaining. There is no bang variant (compare_by_identity! does not exist). Once identity mode is activated on a Hash, it cannot be deactivated—the Hash remains in identity mode for its entire lifetime.

compare_by_identity? — checking the mode

hash.compare_by_identity? -> boolean

The predicate method compare_by_identity? returns true if the Hash is currently using identity comparison for its keys, false if not. By default, a Hash returns false for this predicate.

Use this to query the Hash’s mode without altering it.

Identity vs equality — key distinction

The difference between identity and equality matters most with mutable or non-frozen objects. Consider strings:

s0 = String.new('x')
s1 = String.new('x')

s0.object_id == s1.object_id  # => false

h = {}
h[s0] = :first
h[s1] = :second

h.length        # => 1  (keys are equal by value)
h[s0]           # => :second

Ruby automatically deduplicates identical string literals, so using string literals like 'x' may make s0 and s1 the same object. Using String.new guarantees distinct objects.

Now activate identity mode:

h.compare_by_identity

h.length        # => 2  (each object is its own key)
h[s0]           # => :first
h[s1]           # => :second
h['x']          # => nil  (a different string object)

Symbols in Ruby are interned—:foo and 'foo'.to_sym refer to the same object. This means identity mode does not distinguish between symbol literals and dynamically created symbols when the name matches. To demonstrate identity comparison with symbols, use distinct objects:

h = {}.compare_by_identity

# Create two distinct symbol-like objects using a Struct
Key = Struct.new(:name)
k0 = Key.new('foo')
k1 = Key.new('foo')

h[k0] = 1
h[k1] = 2

h[k0]           # => 1
h[k1]           # => 2

Note that compare_by_identity only affects keys. Values are compared and stored normally regardless of the Hash’s comparison mode.

Creating a fresh identity hash in one step

Ruby provides no compare_by_identity! bang method to reset the mode. To create a new Hash already in identity mode, chain the constructor with the setting method:

h = {}.compare_by_identity
h[:a] = 1
h[:b] = 2

Or use Hash.new with a block, then activate:

h = Hash.new { |hash, key| hash[key] = [] }.compare_by_identity

There is no way to deactivate identity mode once set. If you need a Hash with different mode settings, you must create a new one.

Thread safety

Identity mode is per-Hash instance. Each Hash maintains its own comparison mode independently. Thread safety depends on how you use the Hash—Ruby’s Hash class is not inherently thread-safe for concurrent reads and writes to the same instance.

Frozen objects

When a frozen object is used as a key, the Hash treats it like any other object. Identity comparison uses object_id, which remains valid even after an object is frozen. This can be useful when you want to use frozen objects as stable keys without worrying about their contents affecting lookup behavior.

Immediate values

Immediate values (nil, true, false, integers, symbols, floats) have special behavior in Ruby. Small integers and symbols are interned—the same value always refers to the same object. For these types, identity mode behaves the same as equality mode because two equal immediate values are also identical objects. The distinction between identity and equality becomes meaningful only for heap-allocated objects like strings, arrays, or custom class instances.

Practical use cases

Object registries. When tracking a collection of objects where identity matters—such as tracking live instances in a game engine, memoization caches keyed by object instance, or maintaining a set of objects that have been processed—identity-mode hashes avoid accidentally conflating distinct objects.

Avoiding key collisions with mutable objects. If a key object is mutable and its hash or eql? method depends on internal state, identity comparison eliminates the risk that modifying the object corrupts the Hash’s internal structure.

Working with symbols from different sources. Symbols created via to_sym or intern are interned in Ruby. In most contexts, the same sequence of characters produces the same symbol object. Identity mode is useful in edge cases where symbol objects might differ, such as across certain FFI boundaries or with symbol-keyed data structures from external sources.

See Also

  • Hash#has_key? — checks whether a key exists in the hash
  • Hash#merge — combines two hashes; merging an identity hash into another hash does not preserve identity mode on the result
  • Hash#dig — navigates nested hash structures to retrieve values