Question
Ruby <=> Spaceship Operator: Comparison and Comparable
Question
What does Ruby’s <=> (spaceship) operator do? How does it compare values, and is an operator with the same or similar behavior available in other programming languages?
Short Answer
Ruby’s <=> operator compares two objects and reports their ordering. By the end, you will know how to interpret its return values, define it in your own classes, and use it with Ruby’s Comparable module.
Concept
The Ruby <=> operator is called the spaceship operator because its characters resemble a spaceship. It performs a three-way comparison between a left-hand value and a right-hand value.
left <=> right
Its usual results are:
- A negative number when
leftcomes beforeright 0when both values are equivalent for ordering- A positive number when
leftcomes afterright nilwhen Ruby cannot compare the two values
For example:
2 <=> 5 # => -1
5 <=> 5 # => 0
9 <=> 5 # => 1
The exact positive or negative value is usually not important. What matters is its sign: negative, zero, or positive.
This operator matters because it supplies a consistent ordering rule. Ruby uses ordering when sorting collections, comparing values, and working with the Comparable module. Classes such as Integer, Float, String, Symbol, Array, Date, and Time provide comparison behavior where it makes sense.
When you create a custom class, defining <=> tells Ruby how instances of that class should be ordered.
Mental Model
Think of <=> as a race judge comparing two runners:
- A negative result means the runner on the left finished first.
0means they tied.- A positive result means the runner on the right finished first.
nilmeans the judge cannot run this race because the competitors are not comparable.
For example, comparing two book objects by publication year is like asking the judge which book was published first. Once you give Ruby that rule, it can sort an entire shelf of books.
Syntax and Examples
The basic syntax is:
value_a <=> value_b
Comparing numbers
puts 10 <=> 20 # -1
puts 20 <=> 20 # 0
puts 30 <=> 20 # 1
Comparing strings
Strings are compared lexicographically: roughly dictionary order, character by character.
"apple" <=> "banana" # => -1
"banana" <=> "apple" # => 1
"ruby" <=> "ruby" # => 0
Incompatible comparisons
Some values have no meaningful shared ordering. In that case, Ruby returns nil.
1 <=> "1" # => nil
Do not assume every object can be compared to every other object. Check the documentation and behavior of the objects you are using.
Defining <=> in a class
A common pattern is to include Comparable and define <=> using a property that already supports comparison.
class Player
include Comparable
attr_reader :score
def initialize(score)
@score = score
end
def <=>(other)
score <=> other.score
end
end
alice = Player.new(80)
bob = Player.new(95)
puts alice < bob # true
puts alice == bob # false
puts alice <=> bob # -1
Player#<=> delegates the comparison to the numeric score. Including Comparable then provides methods such as <, <=, >, >=, between?, and == based on that ordering rule.
Step by Step Execution
Consider sorting custom objects:
class Task
include Comparable
attr_reader :priority
def initialize(priority)
@priority = priority
end
def <=>(other)
priority <=> other.priority
end
end
tasks = [Task.new(3), Task.new(1), Task.new(2)]
sorted_tasks = tasks.sort
p sorted_tasks.map(&:priority)
Execution trace:
-
Three
Taskobjects are created with priorities3,1, and2. -
tasks.sortneeds a rule for deciding which task comes first. -
Ruby calls
Task#<=>on pairs of tasks while sorting. -
For a priority
3task compared with priority1, this runs:3 <=> 1 # => 1The positive result means priority
3comes after priority1in ascending order. -
Ruby repeats comparisons until the collection is ordered.
-
map(&:priority)extracts the priorities, producing:[1, 2, 3]
The sort operation does not need to know what a Task is. It only needs each task to provide a valid ordering through <=>.
Real World Use Cases
Common uses of <=> include:
- Sorting records: Order products by price, tickets by priority, or articles by publish time.
- Ranking: Compare game scores, leaderboard entries, or competition results.
- Date and time ordering: Determine whether an appointment is before, equal to, or after another appointment.
- Domain objects: Sort custom objects such as invoices by due date or files by size.
- Range-like checks: With
Comparable, test whether a value falls between two other values.
Example: sorting API result objects by a timestamp.
class Event
include Comparable
attr_reader :occurred_at
def initialize(occurred_at)
@occurred_at = occurred_at
end
def <=>(other)
occurred_at <=> other.occurred_at
end
end
events = [
Event.new(Time.new(2025, 6, 3)),
Event.new(Time.new(2025, 6, 1)),
Event.new(Time.new(2025, 6, 2))
]
ordered = events.sort
Here, the event’s timestamp is the single source of truth for its ordering.
Real Codebase Usage
In real Ruby projects, <=> is most often implemented for a domain object that has one clear natural order.
Delegate to a comparable attribute
class Invoice
include Comparable
attr_reader :due_date
def initialize(due_date)
@due_date = due_date
end
def <=>(other)
due_date <=> other.due_date
end
end
This keeps comparison logic short and makes invoices.sort work naturally.
Sort by multiple fields when needed
A common technique is to compare arrays. Ruby arrays compare element by element.
class Ticket
include Comparable
attr_reader :priority, :created_at
def initialize(priority, created_at)
@priority = priority
@created_at = created_at
end
def <=>(other)
[priority, created_at] <=> [other.priority, other.created_at]
end
end
This sorts first by priority; if priorities are equal, it sorts by created_at.
Use sort_by for one-off sorting
If ordering is needed only once, avoid adding <=> to the class. sort_by is often clearer:
users.sort_by(&:last_name)
Define <=> when the class itself has a meaningful, reusable default order. Use sort_by when the ordering is local to one operation.
Common Mistakes
Expecting exactly -1 or 1
Only the sign is guaranteed by the comparison contract. Write code that checks whether the result is negative or positive, not whether it equals one specific number.
result = 10 <=> 20
puts result < 0 # true
Forgetting that comparison may return nil
This is unsafe because nil cannot be compared with 0:
# Broken when the values are not comparable
(1 <=> "1") < 0
Check the result first when values might be incompatible:
result = 1 <=> "1"
puts "Not comparable" if result.nil?
Including Comparable without defining <=>
Comparable needs your class to implement <=>.
class Product
include Comparable
end
# Product.new < Product.new raises an error because <=> is missing.
Define <=> using a property that has a valid order.
Returning true or false from <=>
This is incorrect:
# Broken
class Player
def <=>(other)
score < other.score
end
end
<=> must return a negative number, zero, a positive number, or nil, not a Boolean.
Creating inconsistent ordering
Your comparison must be consistent. If a < b and b < c, then a should also be less than c. Inconsistent rules can make sorting confusing or unreliable.
Using <=> when sort_by is clearer
Do not add a permanent default ordering merely to sort by one temporary property. Prefer:
products.sort_by(&:price)
when price is not the object’s natural, general-purpose order.
Comparisons
| Tool or language feature | Purpose | Best use |
|---|---|---|
Ruby <=> | Reports relative ordering with negative, zero, positive, or nil | Defining a reusable natural order for a class |
Ruby <, >, <=, >= | Answers one yes/no ordering question | Direct conditions such as age >= 18 |
Ruby == | Tests equality | Checking whether two values are equal |
Ruby sort_by | Sorts using a temporary extracted key | One-off sorting, such as users.sort_by(&:name) |
Ruby sort with a block | Sorts using custom comparison logic | Specialized ordering that does not belong on the class |
numbers = [12, 3, 25]
numbers.sort # Uses the elements' <=> method
numbers.sort_by { |n| -n } # Sorts by a calculated key
numbers.sort { |a, b| b <=> a } # Uses an explicit descending comparison
Similar operators in other languages
Several languages use <=>, but its exact meaning depends on the language:
| Language | <=> behavior |
|---|---|
| Ruby | Three-way comparison; commonly negative, 0, positive, or nil |
| Perl | Numeric three-way comparison |
| PHP 7+ | Three-way comparison, generally returning -1, 0, or 1 |
| C++20+ | Three-way comparison operator that can produce comparison-category results |
The shared symbol does not mean the APIs are identical. In particular, Ruby’s use of nil for non-comparable values and its connection to Comparable are Ruby-specific details.
Cheat Sheet
# Basic form
left <=> right
# Meaning
negative_number # left comes before right
0 # equivalent ordering
positive_number # left comes after right
nil # cannot be compared
# Examples
3 <=> 8 # => -1
8 <=> 8 # => 0
8 <=> 3 # => 1
1 <=> "1" # => nil
# Custom comparable class
class Item
include Comparable
attr_reader :rank
def initialize(rank)
@rank = rank
end
def <=>(other)
rank <=> other.rank
end
end
- Include
Comparableto derive<,<=,>,>=, andbetween?. - Return a negative number,
0, a positive number, ornil. - Compare a stable, meaningful attribute.
- Use
[first_key, second_key] <=> [other_first_key, other_second_key]for multi-field ordering. - Prefer
sort_byfor a one-time sort key.
FAQ
What does <=> return in Ruby?
It normally returns a negative number, 0, or a positive number. It returns nil if the values cannot be meaningfully compared.
Why is <=> called the spaceship operator?
The symbol visually resembles a small spaceship: <=>.
Does Ruby’s <=> always return -1, 0, or 1?
Many built-in comparisons do, but code should rely on negative, zero, or positive rather than exact values.
What is the relationship between <=> and Comparable?
Comparable uses the <=> method you define to provide methods such as <, >, <=, >=, and between?.
Why does 1 <=> "1" return nil?
An integer and a string do not have a shared natural ordering in Ruby, so Ruby reports that they are not comparable.
Should I define <=> on every Ruby class?
No. Define it only if objects of that class have a clear, useful default order. For temporary ordering, use sort_by or a sort block.
Which languages also use <=>?
Perl, PHP, and modern C++ use <=>, although their details and return types differ from Ruby’s behavior.
Mini Project
Description
Build a small task-priority model that can sort task objects by urgency and then by creation time. This demonstrates how <=> gives a custom Ruby class a consistent, reusable ordering rule.
Goal
Create sortable Task objects and print them from highest priority to lowest priority, breaking ties by earlier creation time.
Requirements
- Create a
Taskclass with a title, priority, and creation time. - Include Ruby’s
Comparablemodule. - Define
<=>so higher numeric priority is listed first. - When priorities match, list the earlier task first.
- Create several tasks, sort them, and print their titles in order.
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.