Back to Comparable & Comparator
Q2Question 2 of 4

What is the difference between Comparable and Comparator?

Key Concepts
  • Comparable defines one natural ordering inside the class via compareTo(); the class itself decides how instances are compared
  • Comparator defines external ordering outside the class via compare(); multiple different orderings are possible without modifying the class
  • Comparable is in java.lang (no import needed); Comparator is in java.util
  • Java 8 Comparator factory methods: comparing(), comparingInt(), reversed(), thenComparing(), nullsFirst(), nullsLast()
  • TreeSet and TreeMap use Comparable by default; pass a Comparator at construction to override
  • TreeSet/TreeMap use compareTo for equality, NOT equals; if compareTo returns 0, elements are treated as duplicates even if equals returns false
  • Use Comparable for the one default/natural ordering; use Comparator for alternate, context-specific, or multi-field orderings

Definition

Comparable defines one natural ordering inside the class (compareTo); Comparator defines external orderings outside the class (compare) – use Comparable for the default sort, Comparator for alternate sorts.
  • Comparable (java.lang): the class implements compareTo(T other) to define its natural ordering; one ordering per class, baked in
  • Comparator (java.util): a separate object or lambda that implements compare(T a, T b) to define external ordering; multiple orderings possible without modifying the class
  • compareTo returns: negative (this < other), zero (equal), positive (this > other)
  • Use Comparable for THE default sort (accounts by account ID); use Comparator for alternate sorts (by balance, by date, by holder name)

Comparable: the account card knows how to sort itself: "I compare myself by account number." One built-in order. Comparator: the bank manager decides the sort order: "Sort by balance today, sort by name tomorrow, sort by branch next week." The account card does not change – only the external sorting rule changes.

Understanding the Concepts

What is the difference between Comparable and Comparator?

| Feature | Comparable | Comparator |
|---|---|---|
| Package | java.lang | java.util |
| Method | compareTo(T other) | compare(T a, T b) |
| Who defines | The class itself | External class/lambda |
| Number of orderings | One (natural) | Unlimited |
| Modifies the class | Yes | No |
| Used by default | Collections.sort, TreeSet | Passed explicitly |
| Functional interface | No (has compareTo) | Yes (SAM = compare) |

JAVA
package com.bank.model;

import java.math.BigDecimal;
import java.time.LocalDate;

// Comparable: natural ordering by account ID
public class Account implements Comparable<Account> {
    private final String accountId;
    private final String holder;
    private final BigDecimal balance;
    private final LocalDate openDate;

    public Account(String accountId, String holder, BigDecimal balance, LocalDate openDate) {
        this.accountId = accountId;
        this.holder = holder;
        this.balance = balance;
        this.openDate = openDate;
    }

    @Override
    public int compareTo(Account other) {
        return this.accountId.compareTo(other.accountId); // natural order: by ID
    }

    // getters
    public String getAccountId() { return accountId; }
    public String getHolder() { return holder; }
    public BigDecimal getBalance() { return balance; }
    public LocalDate getOpenDate() { return openDate; }

    @Override
    public String toString() {
        return accountId + " (" + holder + ": " + balance + ")";
    }
}
JAVA
package com.bank.demo;

import com.bank.model.Account;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.*;

public class ComparableVsComparatorDemo {
    public static void main(String[] args) {
        List<Account> accounts = new ArrayList<>(List.of(
            new Account("SAV2001", "Rajesh", new BigDecimal("150000"), LocalDate.of(2020, 1, 15)),
            new Account("CUR3001", "Priya", new BigDecimal("75000"), LocalDate.of(2021, 6, 1)),
            new Account("FD4001", "Amit", new BigDecimal("500000"), LocalDate.of(2019, 3, 20))
        ));

        // Comparable: natural order (by account ID)
        Collections.sort(accounts);
        System.out.println("Natural order (Comparable):");
        accounts.forEach(a -> System.out.println("  " + a));

        // Comparator: sort by balance ascending
        accounts.sort(Comparator.comparing(Account::getBalance));
        System.out.println("\nBy balance (Comparator):");
        accounts.forEach(a -> System.out.println("  " + a));

        // Comparator: sort by balance descending, then by name
        accounts.sort(
            Comparator.comparing(Account::getBalance).reversed()
                      .thenComparing(Account::getHolder)
        );
        System.out.println("\nBy balance desc, then name:");
        accounts.forEach(a -> System.out.println("  " + a));
    }
}
TEXT
Natural order (Comparable):
  CUR3001 (Priya: 75000)
  FD4001 (Amit: 500000)
  SAV2001 (Rajesh: 150000)

By balance (Comparator):
  CUR3001 (Priya: 75000)
  SAV2001 (Rajesh: 150000)
  FD4001 (Amit: 500000)

By balance desc, then name:
  FD4001 (Amit: 500000)
  SAV2001 (Rajesh: 150000)
  CUR3001 (Priya: 75000)

What are the Java 8 Comparator factory methods?

Java 8 made Comparators composable and readable:

JAVA
// Basic comparing
Comparator.comparing(Account::getBalance)                    // by balance
Comparator.comparing(Account::getHolder)                     // by holder name
Comparator.comparing(Account::getOpenDate)                   // by open date

// Primitive-specific (avoid autoboxing)
Comparator.comparingInt(Employee::getAge)                    // int field
Comparator.comparingLong(Trade::getTimestamp)                // long field
Comparator.comparingDouble(Product::getPrice)                // double field

// Reverse
Comparator.comparing(Account::getBalance).reversed()         // descending

// Chaining (multi-level sort)
Comparator.comparing(Account::getHolder)
          .thenComparing(Account::getBalance)                // tie-breaker
          .thenComparing(Account::getOpenDate)               // second tie-breaker

// Null handling
Comparator.nullsFirst(Comparator.comparing(Account::getHolder))  // nulls first
Comparator.nullsLast(Comparator.naturalOrder())              // nulls last

// Case-insensitive
Comparator.comparing(Account::getHolder, String.CASE_INSENSITIVE_ORDER)

What is the TreeSet/TreeMap equality gotcha?

TreeSet and TreeMap use compareTo() (or the Comparator's compare()) for equality, NOT equals(). If compareTo() returns 0, the elements are treated as duplicates – even if equals() returns false.

JAVA
package com.bank.demo;

import java.util.*;

public class CompareToEqualityGotcha {
    public static void main(String[] args) {
        // Case-insensitive comparator: "abc".compareTo("ABC") == 0
        TreeSet<String> set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
        set.add("abc");
        set.add("ABC"); // compareTo == 0 → treated as duplicate!
        System.out.println("TreeSet size: " + set.size()); // 1, not 2!
        System.out.println("TreeSet: " + set); // [abc]

        // HashSet uses equals: "abc".equals("ABC") == false
        HashSet<String> hashSet = new HashSet<>();
        hashSet.add("abc");
        hashSet.add("ABC");
        System.out.println("HashSet size: " + hashSet.size()); // 2
    }
}
TEXT
TreeSet size: 1
TreeSet: [abc]
HashSet size: 2

When do you use Comparable vs Comparator?

| Situation | Use |
|---|---|
| Class has one obvious natural ordering | Comparable |
| Sorting by multiple fields at different times | Comparator |
| Cannot modify the class (third-party library) | Comparator |
| Need reverse, null-safe, or chained ordering | Comparator |
| TreeSet/TreeMap default ordering | Comparable |
| TreeSet/TreeMap alternate ordering | Comparator |
| Lambda/method reference for ad-hoc sort | Comparator |

What are common mistakes?

Mistake 1: compareTo inconsistent with equals

JAVA
// WRONG: compareTo returns 0 for accounts with same balance
// but equals checks account ID
@Override public int compareTo(Account other) {
    return this.balance.compareTo(other.balance);
}
@Override public boolean equals(Object o) {
    return o instanceof Account a && a.accountId.equals(this.accountId);
}
// TreeSet with this: two accounts with same balance → one is dropped!
// Fix: add a tiebreaker to compareTo
@Override public int compareTo(Account other) {
    int cmp = this.balance.compareTo(other.balance);
    return cmp != 0 ? cmp : this.accountId.compareTo(other.accountId);
}

Mistake 2: Using subtraction for compareTo with integers

JAVA
// WRONG: can overflow for large values
@Override public int compareTo(Account other) {
    return this.priority - other.priority; // overflow if values are far apart!
}

// CORRECT: use Integer.compare
@Override public int compareTo(Account other) {
    return Integer.compare(this.priority, other.priority);
}

What should a senior engineer know?

- Comparable contract. compareTo must be consistent: if a.compareTo(b) > 0 and b.compareTo(c) > 0, then a.compareTo(c) > 0 (transitivity). Violating this contract can cause TreeMap/TreeSet to behave incorrectly.

- compareTo and equals consistency. The Comparable documentation strongly recommends that compareTo be consistent with equals: a.compareTo(b) == 0 should imply a.equals(b). When this is violated, sorted collections (TreeSet) and hash-based collections (HashSet) behave differently for the same data.

- Comparator.thenComparing prevents duplicate dropping. When two elements compare equal on the primary field, the tiebreaker ensures they are not treated as duplicates in TreeSet.

What are common interview follow-up questions?

  • "What is the return value of compareTo?" – Negative, zero, or positive.
  • "Can a class implement both Comparable and use a Comparator?" – Yes. The Comparator overrides the natural order when passed to TreeSet/Collections.sort.
  • "What happens if compareTo returns 0 in a TreeSet?" – The element is treated as a duplicate and not added.
  • "How do you sort by multiple fields?" – Comparator.comparing().thenComparing().

Visual Overview

DIAGRAM
  Comparable vs Comparator

  Comparable (inside the class):
  ┌──────────────────────────────────┐
  │ class Account implements         │
  │   Comparable<Account> {          │
  │   compareTo(Account other) {     │
  │     return id.compareTo(other.id)│
  │   }                              │
  │ }                                │
  │ ONE ordering: by account ID      │
  └──────────────────────────────────┘

  Comparator (outside the class):
  ┌──────────────────────────────────┐
  │ Comparator.comparing(::balance)  │ → by balance
  │ Comparator.comparing(::holder)   │ → by holder
  │ Comparator.comparing(::date)     │ → by date
  │ MULTIPLE orderings, no class mod │
  └──────────────────────────────────┘

  TreeSet equality:
  compareTo(a, b) == 0 → treated as SAME element
  (even if equals returns false!)

Key Takeaway

Comparable and Comparator are the two ways to define ordering in Java. Comparable is baked into the class: one natural ordering via compareTo. Comparator is external: unlimited orderings via compare, composable with Java 8 factory methods like comparing, thenComparing, reversed, and nullsFirst.

Use Comparable for the one default sort (accounts by ID, trades by trade number). Use Comparator for everything else: sorting by balance, by date, by holder name, or any combination. Java 8 Comparators compose beautifully: Comparator.comparing(Account::getBalance).reversed().thenComparing(Account::getHolder) reads like English.

The critical gotcha: TreeSet and TreeMap use compareTo for equality, not equals. If compareTo returns 0, the elements are treated as duplicates. In the National Bank of India's account leaderboard, Rajesh's comparator sorts by balance and uses account ID as a tiebreaker to prevent accounts with identical balances from being silently dropped.

Remember one sentence: Comparable is the class's built-in ordering; Comparator is an external, composable ordering strategy – and TreeSet treats compareTo-0 as equal, not equals.

---

Share
Use ← → arrow keys to navigate