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
Fictionmight===a particular book
This is why Ruby keeps separate methods: each answers a different question.
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
aandbhave the same string content, soa == bistrue- They are different objects, so
a.equal?(b)isfalse creferences the exact same object asa, soa.equal?(c)istrue
Example 2: eql? with numbers
p 1 == 1.0 # true
p 1.eql?(1.0) # false
Explanation
==compares numeric value more loosely hereeql?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
Pointobjects are different instances - But
==says they are equal by value eql?andhashmake them behave correctly asHashkeys
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.
ais 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
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
| Method | Main meaning | Typical use | Usually overridden? | Example |
|---|---|---|---|---|
equal? | Same object identity | Low-level identity checks | Rarely | a.equal?(b) |
== | Same value | Normal application logic | Very often | user.id == other.id |
eql? | Hash-key equality | Hash and Set behavior | Often with hash | 1.eql?(1.0) # false |
=== | Match / case equality | case statements, pattern-like checks | Often in core classes | (1..5) === 3 |
== vs eql?
| Comparison | Result | Why |
|---|---|---|
1 == 1.0 | true | Numeric values are considered equal |
1.eql?(1.0) | false | eql? is stricter for hash-key equality |
== vs equal?
| Comparison | Result | Why |
|---|---|---|
"a" == "a" | true | Same value |
"a".equal?("a") | Usually false | Different string objects |
== vs ===
| Expression | Result | Meaning |
|---|---|---|
"abc" == "abc" | true | Same string value |
String === "abc" | true | Object matches the class String |
/abc/ === "xxabcxx" | true | String matches regex |
(1..3) === 2 | true | Value falls in the range |
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 withhashfor custom hash keys - Use
===mainly incase-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 equalityeql?= 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.
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
==andeql?using1and1.0. - Define a custom class that implements
==,eql?, andhash. - Use a
casestatement that relies on===with a range or class. - Print clear labels so the output is easy to understand.
Keep learning
Related questions
Add a Default Value to an Existing Column in a Rails Migration
Learn how to add or change a default value on an existing database column using Rails Active Record migrations safely.
Calling a Class Method from an Instance in Ruby
Learn how to call a class method from an instance in Ruby using self.class, with examples, pitfalls, and practical usage patterns.
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.