skip to content

In Ruby, what is the difference between ==, eql? and equal?, and why is 1 == 1.0 true while 1.eql?(1.0) is false?

level: juniorimportance: must knowfreq 82%

answer

  1. three questions, three strictness levels
  2. identity: same object_id
  3. value: overridden per class
  4. hash-key equality, no conversion
  5. {1 => :a}[1.0] is nil

basics

~20 s

equal? asks whether both are the same object; == compares values and is overridden per class; eql? is the stricter hash-key equality. Numeric == compares across types, so 1 == 1.0, but eql? requires the same class.

solid answer

~40 s

`equal?` is **identity**: true only for the very same object (same `object_id`), and it should never be overridden. `==` is **value equality**, which classes override: two separately built strings with the same characters are `==` but not `equal?`. `eql?` is the equality `Hash` uses for keys (together with `hash`); for most classes it matches `==`, but numeric classes make it stricter. `Integer#==` compares across numeric types, so `1 == 1.0` is `true`, while `eql?` also requires the same type, so `1.eql?(1.0)` is `false`. The practical effect: `{1 => :one}[1.0]` returns `nil`, and `[1, 1.0].uniq` keeps both elements. On a plain `Object`, all three compare identity until a class overrides them.

code

ruby · 11 lines
ruby
a = "ruby"
b = a.dup

a == b          # => true   same characters
a.eql?(b)       # => true   String#eql? also compares content
a.equal?(b)     # => false  two different objects
a.object_id == b.object_id  # => false

1 == 1.0        # => true
1.eql?(1.0)     # => false
{1 => :one}[1.0]  # => nil

go deeper

for a junior

State what each of the three asks: same object, same value, same hash key; give 1 == 1.0 versus 1.eql?(1.0) as the example.

for a middle

Tie eql? and hash to Hash lookups and uniq, explain the numeric exception, and know that plain objects compare by identity in all three.

for a senior

Spot lookups that miss because a key arrived as 1.0 instead of 1, or as a copy of a value object without eql? and hash, and fix the key type or the class.

for a principal

Set the rule for value classes in a codebase: prefer Data.define or override ==, eql? and hash together, and never let equality depend on mutable state.

## Three kinds of sameness Ruby gives every object three equality methods, each answering a different question: | Method | Question it answers | Default on Object | Typically overridden? | |---|---|---|---| | `equal?` | Is this the **same object**? | identity | never | | `==` | Do these have the **same value**? | identity | yes, by most value classes | | `eql?` | Are these the **same hash key**? | identity | yes, together with `hash` | The default for all three is identity, so for a class you write without overriding anything, `a == b` is only true when `a` and `b` are the same object. `!=` is simply the negation of `==`. ## `equal?` and `object_id` `equal?` is **identity**. Every live object has an `object_id`, an Integer that stays the same for the object's lifetime and is never shared by two live objects, and `a.equal?(b)` is true exactly when they are the same object. The core documentation says `equal?` should never be overridden, because code relies on it to mean identity. A few kinds of objects are reused by the interpreter, so identity holds even between values you built separately: - `nil`, `true`, `false`, small integers and symbols are **immediate values**; `(21 * 2).equal?(42)` is true. - Two separately built strings are different objects: `"ruby".equal?("ruby".dup)` is false. ## `==`: value equality `==` is what most code calls, and classes override it to compare contents: - `String#==` compares characters. - `Array#==` compares elements pairwise with `==`. - `Integer#==` and `Float#==` compare **numeric value across types**, so `1 == 1.0` and `1 == Rational(1, 1)` are true. ## `eql?`: equality for hash keys `eql?` exists so that `Hash` (and `Array#uniq`, and `Set`) can decide whether two objects are the same **key**. The rule, stated in the core docs, is that whenever `a.eql?(b)` is true, `a.hash == b.hash` must also be true, so a class that overrides one overrides both. Most classes make `eql?` behave like `==`. Numbers are the famous exception: `Integer#eql?` returns true only if the other object is also an Integer with the same value. So: ```ruby 1 == 1.0 # => true (numeric comparison across types) 1.eql?(1.0) # => false (different classes) 1.equal?(1) # => true (small integers are immediate) prices = {1 => :one} prices[1.0] # => nil [1, 1.0].uniq # => [1, 1.0] ``` The same split shows up in value classes built with `Data.define` or `Struct`: their `==` compares members with `==`, while their `eql?` compares members with `eql?`, so a member of `1` versus `1.0` makes them `==` but not `eql?`. ## Quick checks that trip people up - `[1, 2] == [1.0, 2.0]` is **true**, because `Array#==` compares elements with `==`; `[1, 2].eql?([1.0, 2.0])` is **false**, because `Array#eql?` compares them with `eql?`. - `"a" == :a` is **false**: a String and a Symbol are never `==`, even with the same text. - `nil == false` is **false**: both are falsy, but they are different objects of different classes. - `1.0.eql?(1.0)` is **true**: same class, same value. Each of these follows from one rule: `==` is whatever the receiver's class defines as "same value", and `eql?` adds "and usable as the same hash key". ## Putting it together 1. Use `==` in ordinary code; it is the method classes intend you to call. 2. Use `equal?` only when you really need "the same object", for example to detect that a method received the very object it already holds. 3. Remember that hash-based collections use `eql?` and `hash`, so a lookup can miss even though `==` says the values match. 4. When you write your own value class, override `==`, `eql?` and `hash` together. Two other methods are easy to confuse with these but answer different questions: `===` is case equality used by `case`/`when`, and `<=>` is ordering, from which `Comparable` derives its own `==`. ## Why interviewers ask it It is the Ruby version of the classic identity-versus-equality screen, with a twist that separates candidates: the third method, `eql?`, and the fact that numbers make it stricter than `==`. A candidate who connects `eql?` to `Hash` lookups and explains `{1 => :one}[1.0]` returning `nil` has understood why the three exist.

  • What do ==, eql? and equal? do on an instance of a class you wrote without overriding anything?
    All three compare identity. `==` and `equal?` come from `BasicObject` and `eql?` from `Kernel`, and each returns true only when both sides are the same object. Two `Point.new(1, 2)` objects are therefore not `==`, and a `Hash` treats them as different keys until the class overrides `==`, `eql?` and `hash`.
  • Why can't you rely on object_id to compare two strings with the same text?
    `object_id` identifies an object, not its contents. Two strings built separately, such as `"ruby"` and `"ruby".dup`, are distinct objects with distinct ids, even though they are `==`. Only immediate values such as small integers, symbols and `nil`, or objects the interpreter deliberately reuses, share an id.

saying these in an interview costs you the question

  • equal? compares values and == compares object identity.
  • eql? is just an alias of == for every class, including numbers.
  • Because 1 == 1.0, the key 1.0 finds the entry stored under 1 in a Hash.
  • Overriding equal? is the normal way to define equality for a value class.
  • Two strings with the same text always share an object_id.