← Reference · Nestor G Pestelos Jr · Print this page

Programming Languages · Ruby

Ruby User-Defined Hash Keys

Reference entry · last updated September 21, 2026

Previous version (20260921)

A user-defined hash key in Ruby is an object whose equality the program defines. A Hash calls the key's hash method to choose a bucket, then calls eql? to decide whether a key already in that bucket is the same key. The inherited Object versions make every object a distinct key, so a class whose instances should match by value overrides both methods. Overriding eql? alone is not enough: two equal keys that hash differently land in different buckets and both survive as separate keys.[1][2]

First principles and definitions

Identity and equality

Ruby separates three questions. equal? asks whether two references point at the same object, and it is never overridden. == asks whether two objects are equal, and classes define what that means. eql? is the stricter form that a Hash uses, and for Object it is synonymous with ==.[2]

The split between == and eql? is easiest to see with numbers. == converts across numeric types, but eql? does not, which keeps 1 and 1.0 as different hash keys.[2]

1 == 1.0      # => true
1.eql?(1.0)   # => false

How a Hash looks a key up

To be usable as a hash key, an object must implement hash and eql?. The hash value selects a bucket, and eql? is then used to test members inside that bucket for equality. Object#hash returns an Integer for this purpose.[1][2]

This two-step lookup is why both methods must agree. The bucket choice is a fast filter, and eql? is the exact test. A key that skips the first step is never compared.

The hash and eql? invariant

The required property is: if a.eql?(b) is true, then a.hash must equal b.hash.[2] Two objects that are equal by eql? but hash differently break the lookup, because they never reach the same bucket.

eql? should also be symmetric and transitive, and the pair must stay consistent: two keys that are eql? must return the same hash, and the hash value must not change while the key is stored.

Overriding eql? and hash

Override both when two distinct objects should be the same key because their data is equal, and identity is not the point. Typical cases are a domain value object, a composite identifier used for deduplication, or a cache key.

Derive hash from the fields used for equality. Aliasing eql? to == is safe only when the field comparisons agree with their hashes. Checking the outer class does not enforce this: 1 == 1.0 is true, while 1.eql?(1.0) is false. This example compares each field with eql?.[2]

class Book
  attr_reader :author, :title

  def initialize(author, title)
    @author = author
    @title = title
  end

  def ==(other)
    self.class == other.class &&
      other.author.eql?(@author) &&
      other.title.eql?(@title)
  end
  alias eql? ==

  def hash
    [self.class, @author, @title].hash
  end
end

book1 = Book.new('matz', 'Ruby in a Nutshell')
book2 = Book.new('matz', 'Ruby in a Nutshell')

reviews = {}
reviews[book1] = 'Great reference!'
reviews[book2] = 'Nice and compact!'

reviews.length   # => 1

Without the overrides, book1 and book2 are two distinct keys and the hash holds two entries.

When it does not make sense

Alternatives

Struct bundles members and defines member-based eql? and hash, so its instances work as value keys without hand-written overrides.[3] Data, added in Ruby 3.2, is the immutable counterpart for the same purpose.[4]

When the key is a composite, a plain Array or a joined String is often simpler than a custom class, because the built-in classes already satisfy the contract. Reach for a custom key when the object carries behavior or identity that the key must represent.

Choosing

References

  1. ^ Ruby 3.3 core documentation, "Hash," User-Defined Hash Keys and compare_by_identity. Free full text: docs.ruby-lang.org/en/3.3/Hash.html
  2. ^ Ruby 3.3 core documentation, "Object," eql?, hash, and equal?. Free full text: docs.ruby-lang.org/en/3.3/Object.html
  3. ^ Ruby 3.3 core documentation, "Struct." Free full text: docs.ruby-lang.org/en/3.3/Struct.html
  4. ^ Ruby 3.3 core documentation, "Data." Free full text: docs.ruby-lang.org/en/3.3/Data.html