Question

Ruby Equality Methods Explained: equal?, eql?, ==, and ===

rubycomparisonoperatorsequality

Question

I am trying to understand the difference between Ruby’s four equality-related methods: equal?, eql?, ==, and ===.

I know that, by default, == eventually relies on object identity behavior similar to equal?, which returns true when both operands refer to exactly the same object.

I also read that === often delegates to == by default. If none of these methods are overridden, does that mean ===, ==, and equal? all behave the same way?

Then there is eql?. What does eql? do by default? Does it depend on something like an object’s hash or object_id?

More broadly, why does Ruby have so many different equality methods? Are they intended to represent different meanings of equality?

Short Answer

By the end of this page, you will understand the purpose of Ruby’s four equality methods, how they differ, and when each one is used. You will see that they are not redundant: equal? checks object identity, == usually checks value equality, eql? is mainly used for Hash key equality, and === is used for case matching and pattern-like comparisons.

Concept

Ruby has multiple equality methods because equality can mean different things depending on context.

The four meanings of equality in Ruby

  • equal? → Are these the exact same object in memory?
  • == → Do these two objects represent the same value?
  • eql? → Are these objects equal in a way that is strict enough for hash keys?
  • === → Does the left-hand object consider the right-hand object a match?

These methods are related, but they serve different purposes.

equal? — object identity

equal? comes from BasicObject and is about identity, not value.

s1 = "ruby"
s2 = "ruby"
s3 = s1

p s1.equal?(s2) # false
p s1.equal?(s3) # true

Here, s1 and s2 contain the same text, but they are different string objects. s3 points to the exact same object as s1.

In practice, equal? is rarely overridden and is used when you truly care whether two references point to the same object.

== — value equality

== is the method most Ruby code uses for normal comparison.

Many classes override it so that two different objects can still be considered equal if their contents match.

p "ruby" == "ruby"   # true
p [1, 2] == [1, 2]     # true

These objects are not the same object, but Ruby considers them equal in value.

If a class does not override ==, it inherits a default behavior similar to identity comparison.

eql? — hash equality

eql? is mostly important for Hash keys.

Ruby uses both hash and eql? together to decide whether two keys should be treated as the same key.

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

This is an important difference. == says these numeric values are equal, but eql? is stricter and says they are not the same kind of hash key.

That means:

h = {}
h[1] = "integer"
h[1.0] = "float"

p h
# {1=>"integer", 1.0=>"float"}

Ruby keeps them as separate keys because 1.eql?(1.0) is false.

eql? does not simply call object_id. It works together with hash, and classes that override eql? should also make sure compatible objects return the same hash value.

=== — case equality or matching

=== is often called case equality, but it is better to think of it as matching.

It is heavily used by case statements.

case "cat"
when String
  puts "It's a string"
end

Ruby evaluates that roughly like this:

String === "cat"

For classes and modules, === checks whether an object is an instance of that class or module.

p String === "cat" # true
p Numeric === 10    # true

For ranges, it checks membership:

p (1..5) === 3      # true

For regexes, it checks pattern matching:

p(/cat/ === "a cat") # true

So while === may delegate to == for some objects, many core Ruby classes give it special matching behavior.

Why Ruby has all four

Ruby separates these methods because programs need different ideas of sameness:

  • identity comparison
  • value comparison
  • hash-key comparison
  • case/matching comparison

Using one method for everything would make the language much less expressive.

Mental Model

Think of four ways to ask whether two things are “the same”:

  • equal? → Is this the exact same physical book?
  • == → Do these two books have the same content?
  • eql? → Should a filing system treat these as the same catalog entry?
  • === → Does this category include this item?

Example:

  • Two copies of the same novel are not equal?
  • They may be == if you compare by title and content
  • A library catalog may use stricter rules like eql?
  • The category Fiction might === a particular book

This is why Ruby keeps separate methods: each answers a different question.

Take Quiz

Syntax and Examples

Core syntax

obj1.equal?(obj2)
obj1 == obj2
obj1.eql?(obj2)
obj1 === obj2

Example 1: identity vs value

a = "hi"
b = "hi"
c = a

p a.equal?(b) # false
p a == b      # true
p a.equal?(c) # true

Explanation

  • a and b have the same string content, so a == b is true
  • They are different objects, so a.equal?(b) is false
  • c references the exact same object as a, so a.equal?(c) is true

Example 2: eql? with numbers

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

Explanation

  • == compares numeric value more loosely here
  • eql? is stricter and distinguishes integer from float

Example 3: === in a case statement

value = 7

case value
when 1..10
  puts "between 1 and 10"
when String
  puts "a string"
end

Ruby checks this roughly as:

(1..10) === value
String === value

So === is about matching, not just ordinary equality.

Example 4: custom == and eql?

class Point
  attr_reader :x, :y

  def initialize(x, y)
    @x = x
    @y = y
  end

  def ==(other)
    other.is_a?(Point) && x == other.x && y == other.y
  end

  def eql?(other)
    other.is_a?(Point) && x.eql?(other.x) && y.eql?(other.y)
  end

  def hash
    [x, y].hash
  end
end

p1 = Point.new(2, 3)
p2 = Point.new(2, 3)

p p1 == p2       # true
p p1.equal?(p2)  # false

Explanation

  • The two Point objects are different instances
  • But == says they are equal by value
  • eql? and hash make them behave correctly as Hash keys

Step by Step Execution

Traceable example

a = "ruby"
b = "ruby"
c = a

puts a.equal?(b)
puts a == b
puts a.equal?(c)
puts String === a

Step by step

1. a = "ruby"

Ruby creates a string object and stores its reference in a.

2. b = "ruby"

Ruby creates another string object with the same content and stores its reference in b.

3. c = a

c now points to the same object as a.

4. a.equal?(b)

Ruby checks whether a and b are the exact same object.

  • They are not
  • Output: false

5. a == b

Ruby checks whether the two strings have equal value.

  • Both contain "ruby"
  • Output: true

6. a.equal?(c)

Ruby checks whether a and c point to the same object.

  • They do
  • Output: true

7. String === a

Ruby checks whether a matches String.

  • a is a string
  • Output: true

Final output

false
true
true
true

Real World Use Cases

Where these methods appear in real Ruby code

== for normal comparisons

Used when comparing values in everyday code:

if user.role == "admin"
  puts "Allow access"
end

eql? and hash for Hash keys and sets

Used when objects are stored in hashes or sets:

scores = {}
scores["alice"] = 10
scores["alice"] = 20

Ruby needs eql? and hash to decide whether the second key is the same as the first.

=== in routing, validation, and case logic

case params[:status]
when "pending", "processing"
  puts "In progress"
when "done"
  puts "Finished"
end

Also useful with ranges:

case age
when 0..12
  puts "child"
when 13..19
  puts "teen"
end

And with regexes:

case email
when /@/
  puts "looks like an email"
end

equal? for identity-sensitive checks

This is less common, but sometimes useful when checking:

  • whether two variables point to the same mutable object
  • whether a special singleton object is being used
  • whether an optimization reused an object reference
Take Quiz

Real Codebase Usage

Common patterns in real projects

Guard clauses with ==

def cancel_order(order)
  return unless order.status == "pending"

  order.status = "cancelled"
end

This is the most common equality style in application code.

case statements powered by ===

def label(value)
  case value
  when String
    "text"
  when Integer
    "whole number"
  when 1..100
    "small range value"
  else
    "unknown"
  end
end

Custom value objects implementing ==, eql?, and hash

When creating domain objects such as coordinates, money, or IDs, developers often define value-based equality.

class Money
  attr_reader :amount, :currency

  def initialize(amount, currency)
    @amount = amount
    @currency = currency
  end

  def ==(other)
    other.is_a?(Money) && amount == other.amount && currency == other.currency
  end

  def eql?(other)
    self == other
  end

  def hash
    [amount, currency].hash
  end
end

Validation and matching with regex ===

def valid_zip?(zip)
  /\A\d{5}\z/ === zip
end

Many developers would use match? directly here, but it shows how === supports matching semantics.

Defensive comparisons

When writing ==, real code often checks type first:

def ==(other)
  return false unless other.is_a?(self.class)

  id == other.id
end

This avoids errors and keeps equality predictable.

Common Mistakes

1. Using equal? when you mean value equality

Broken example:

puts "ruby".equal?("ruby")

A beginner may expect true, but this usually returns false because these are different string objects.

Use this instead:

puts "ruby" == "ruby"

2. Assuming === means the same as ==

Broken assumption:

puts String === "abc"  # true
puts String == "abc"   # false

=== is often about matching, especially in case statements.

3. Overriding eql? without overriding hash

Broken design:

class Point
  def initialize(x, y)
    @x = x
    @y = y
  end

  def eql?(other)
    other.is_a?(Point) && @x == other.instance_variable_get(:@x) && @y == other.instance_variable_get(:@y)
  end
end

If you override eql?, you should also override hash. Otherwise, Hash and Set may behave incorrectly.

Correct idea:

def hash
  [@x, @y].hash
end

4. Assuming eql? uses object_id

It does not simply compare identity. Its role is stricter logical equality, especially for hash keys.

5. Writing unsafe custom == methods

Broken example:

def ==(other)
  x == other.x && y == other.y
end

This can raise an error if other is not the expected type.

Safer version:

def ==(other)
  other.is_a?(Point) && x == other.x && y == other.y
end

6. Forgetting that some immediate values behave differently

For values like symbols, integers, true, false, and nil, identity and value comparisons can look the same more often than with mutable objects. Do not build your mental model only from those examples.

Comparisons

Equality methods compared

MethodMain meaningTypical useUsually overridden?Example
equal?Same object identityLow-level identity checksRarelya.equal?(b)
==Same valueNormal application logicVery oftenuser.id == other.id
eql?Hash-key equalityHash and Set behaviorOften with hash1.eql?(1.0) # false
===Match / case equalitycase statements, pattern-like checksOften in core classes(1..5) === 3

== vs eql?

ComparisonResultWhy
1 == 1.0trueNumeric values are considered equal
1.eql?(1.0)falseeql? is stricter for hash-key equality

== vs equal?

ComparisonResultWhy
"a" == "a"trueSame value
"a".equal?("a")Usually falseDifferent string objects

== vs ===

ExpressionResultMeaning
"abc" == "abc"trueSame string value
String === "abc"trueObject matches the class String
/abc/ === "xxabcxx"trueString matches regex
(1..3) === 2trueValue falls in the range
Take Quiz

Cheat Sheet

Quick reference

a.equal?(b) # same object?
a == b      # same value?
a.eql?(b)   # same hash-key equality?
a === b     # does a match b?

Rules of thumb

  • Use == for most comparisons
  • Use equal? only when identity matters
  • Use eql? together with hash for custom hash keys
  • Use === mainly in case-style matching

Important examples

"x" == "x"           # true
"x".equal?("x")      # usually false
1 == 1.0               # true
1.eql?(1.0)            # false
String === "hello"    # true
(1..5) === 3           # true
/ell/ === "hello"     # true

Custom object rule

If you override eql?, also override hash:

def eql?(other)
  other.is_a?(MyClass) && value == other.value
end

def hash
  value.hash
end

Default mental shortcut

  • equal? = identity
  • == = loose value equality
  • eql? = strict equality for hashes
  • === = matching

FAQ

What is the difference between == and equal? in Ruby?

== usually checks whether two objects have the same value. equal? checks whether they are the exact same object.

What is eql? used for in Ruby?

eql? is mainly used by Hash and Set to determine whether two keys should be treated as the same key. It should be consistent with hash.

Is === the same as == in Ruby?

Not always. === is often used for matching, especially in case statements. Classes, ranges, and regexes often give it special behavior.

Why does 1 == 1.0 return true, but 1.eql?(1.0) returns false?

== compares numeric value, while eql? is stricter and distinguishes integer and float for hash-key purposes.

Should I use equal? in normal Ruby code?

Usually no. Most of the time, == is the right choice. Use equal? only when object identity specifically matters.

When should I override eql? in a custom Ruby class?

Override it when instances of your class will be used as Hash keys or in Set, and define a matching hash method too.

How does case use === in Ruby?

In a case statement, Ruby calls when_value === case_value. That is why ranges, regexes, and classes work naturally in when clauses.

Take Quiz

Mini Project

Description

Create a small Ruby program that compares values using all four equality methods. This project helps you see the difference between object identity, value equality, hash-key equality, and case matching in one place.

Goal

Build a script that prints comparison results for strings, numbers, custom objects, and case matching.

Requirements

  • Create examples that demonstrate equal? and == with strings.
  • Show the difference between == and eql? using 1 and 1.0.
  • Define a custom class that implements ==, eql?, and hash.
  • Use a case statement that relies on === with a range or class.
  • Print clear labels so the output is easy to understand.
Take Quiz

Keep learning

Related questions

View more