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 structure 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 collection remains in identity mode for its entire lifetime. To check whether a hash is currently using identity comparison, call the predicate method.
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, it returns false for this predicate 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.
When two distinct String objects hold the same characters, the default equality mode treats them as the same key. Identity mode changes that by using object_id for lookups, so each object reference becomes a separate entry regardless of its contents.
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 collection’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
When you need a default value for missing keys, chain the constructor with compare_by_identity. The block that supplies defaults works the same way regardless of the comparison mode, so you get both identity-based key lookup and automatic fallback values in one expression.
Or use Hash.new with a block, then activate:
h = Hash.new { |hash, key| hash[key] = [] }.compare_by_identity
Once identity mode is active on a hash, it stays on for the lifetime of that instance. If you later need equality-based lookups, you must create a new hash. Plan for this at the point where you decide to activate identity comparison.
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
A quick check to confirm identity mode is active:
h = {}.compare_by_identity
h.compare_by_identity? # => true
h[:a] = 1
h[:a] = 2
h[:a] # => 2 (same key object, overwrites)
Identity mode is per-Hash instance. Each instance maintains its own comparison mode independently. Thread safety depends on how you use the collection—Ruby’s Hash class is not inherently thread-safe for concurrent reads and writes to the same instance.
Frozen objects
Comparing frozen keys by identity:
h = {}.compare_by_identity
a = "key".freeze
b = "key".freeze
h[a] = 1
h[b] = 2
h[a] # => 1
h[b] # => 2 (distinct frozen objects)
When a frozen object is used as a key, it treats the key 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. For immediate values like integers and symbols, identity and equality produce the same result because equal values share the same object reference in Ruby.
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 internal bucket 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