Question

Ruby <=> Spaceship Operator: Comparison and Comparable

rubyoperatorscomparison-operatorsspaceship-operator

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 left comes before right
  • 0 when both values are equivalent for ordering
  • A positive number when left comes after right
  • nil when 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.
  • 0 means they tied.
  • A positive result means the runner on the right finished first.
  • nil means 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.

Take Quiz

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:

  1. Three Task objects are created with priorities 3, 1, and 2.

  2. tasks.sort needs a rule for deciding which task comes first.

  3. Ruby calls Task#<=> on pairs of tasks while sorting.

  4. For a priority 3 task compared with priority 1, this runs:

    3 <=> 1 # => 1
    

    The positive result means priority 3 comes after priority 1 in ascending order.

  5. Ruby repeats comparisons until the collection is ordered.

  6. 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.

Take Quiz

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 featurePurposeBest use
Ruby <=>Reports relative ordering with negative, zero, positive, or nilDefining a reusable natural order for a class
Ruby <, >, <=, >=Answers one yes/no ordering questionDirect conditions such as age >= 18
Ruby ==Tests equalityChecking whether two values are equal
Ruby sort_bySorts using a temporary extracted keyOne-off sorting, such as users.sort_by(&:name)
Ruby sort with a blockSorts using custom comparison logicSpecialized 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
RubyThree-way comparison; commonly negative, 0, positive, or nil
PerlNumeric 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.

Take Quiz

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 Comparable to derive <, <=, >, >=, and between?.
  • Return a negative number, 0, a positive number, or nil.
  • Compare a stable, meaningful attribute.
  • Use [first_key, second_key] <=> [other_first_key, other_second_key] for multi-field ordering.
  • Prefer sort_by for 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.

Take Quiz

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

  1. Create a Task class with a title, priority, and creation time.
  2. Include Ruby’s Comparable module.
  3. Define <=> so higher numeric priority is listed first.
  4. When priorities match, list the earlier task first.
  5. Create several tasks, sort them, and print their titles in order.
Take Quiz

Keep learning

Related questions

View more