Comparable vs Comparator in Java: The Sorting Confusion Every Interview Tests

Comparableis implemented by the class itself for natural ordering;Comparatoris an external object that supports multiple orderings without touching the class.compareToreturns negative/zero/positive to signal less-than/equal/greater-than; only the sign matters, not the magnitude.- The integer overflow bug: never use subtraction (
a - b) in a comparator; always useInteger.compare(a, b)instead. TreeSetandTreeMapusecompareTo(orcompare) to define equality, notequals(); violating the consistent-with-equals contract causes silent bugs.- Java 8
Comparatorchaining (Comparator.comparing().thenComparingInt().reversed()) handles multi-field sorting cleanly and signals modern Java fluency. - When you cannot modify a class,
Comparatoris the only option; when you need one canonical order baked into the type, implementComparable.
You have a list of Employee objects and you want to sort them. You call Collections.sort(employees). It explodes.
java.lang.ClassCastException: Employee cannot be cast to java.lang.Comparable
Congratulations. Java does not know what "less than" means for your class, and it is not going to guess. This is the entire Comparable vs Comparator problem: you have a custom object and Java needs someone to teach it how ordering works. Two interfaces do that job. They do it in completely different ways, and interviewers love to probe exactly where you've internalized the difference.
Why Custom Sorting Breaks
For primitives and strings, Java ships with a definition of order. int has natural numeric order. String compares lexicographically. Your Employee class has a name, a salary, a department, a hire date, and Java has absolutely no idea which one you care about.
When you sort a collection of custom objects, you have to tell Java what the ordering rule is. You can bake that rule into the class, or you can supply it from outside. That single design decision is the entire Comparable vs Comparator story.
Comparable: The Object Knows Its Own Order
Comparable<T> is an interface your class implements. You're making a promise: "this object knows how to compare itself to another object of the same type."
One method: compareTo(T other).
public class Employee implements Comparable<Employee> { String name; int salary; public Employee(String name, int salary) { this.name = name; this.salary = salary; } @Override public int compareTo(Employee other) { return Integer.compare(this.salary, other.salary); } }
Now Collections.sort(employees) works. So does putting Employee objects into a TreeSet or using them as keys in a TreeMap. The sort order is baked into the class itself, forever.
compareTo returns a negative number if this is less than other, zero if equal, positive if greater. Only the sign matters, not the magnitude.
This is called the natural ordering. There can only be one natural ordering per class. You pick salary, you're stuck with salary everywhere that uses Comparable. Want to sort by name somewhere else? Keep reading.
Comparator: An External Referee
Comparator<T> is a separate object that knows how to compare two instances of T. It lives outside the class being compared. Think of it as a third party that you bring in whenever the class's own opinion isn't what you need.
Two inputs, one comparison. Method signature: compare(T a, T b).
Comparator<Employee> byName = (a, b) -> a.name.compareTo(b.name); Comparator<Employee> bySalaryDesc = (a, b) -> Integer.compare(b.salary, a.salary); employees.sort(byName); Collections.sort(employees, bySalaryDesc);
You can have as many Comparators as you want for the same class, without touching Employee.java at all.
Use Comparator when:
- The class belongs to a library you can't modify
- You need multiple different orderings for the same type
- You want the sorting logic separate from the data model (HR wants alphabetical; payroll wants salary descending; your manager wants "most recently promoted, I don't care how you do it")

The Integer Overflow Bug Nobody Warns You About
When implementing compareTo or compare, beginners write this:
// Looks right. It's wrong. return this.salary - other.salary;
It passes your test cases. It will silently corrupt a sort the moment someone earns a very large or very small salary. This causes integer overflow when values span a wide range. If this.salary is Integer.MIN_VALUE (-2,147,483,648) and other.salary is any positive number, subtraction wraps around to a positive result. Wrong sign, broken sort, no exception thrown.
The fix is one word longer:
return Integer.compare(this.salary, other.salary); return Double.compare(this.score, other.score); return Long.compare(this.id, other.id);
Interviewers notice when candidates write the subtraction version. Point it out yourself before they do.
The "Consistent with Equals" Trap
This one is subtle enough to make it into production code written by experienced developers.
TreeSet uses compareTo (or compare) to decide if two elements are the same. HashSet uses equals. If your compareTo returns 0 for two objects whose equals returns false, you get different behavior depending on which Set you use.
// Compares by salary only @Override public int compareTo(Employee other) { return Integer.compare(this.salary, other.salary); } Employee alice = new Employee("alice", 80000); Employee bob = new Employee("bob", 80000); Set<Employee> hashSet = new HashSet<>(); hashSet.add(alice); hashSet.add(bob); System.out.println(hashSet.size()); // 2, because equals() differs Set<Employee> treeSet = new TreeSet<>(); treeSet.add(alice); treeSet.add(bob); System.out.println(treeSet.size()); // 1, because compareTo() returns 0
Bob just silently vanished. No exception, no warning, just gone. If your code processes employee records and you later swap HashSet for TreeSet for ordering reasons, you've just introduced a data-loss bug that shows up in production three months later.
The contract says: when compareTo returns 0, equals should return true. Violating it doesn't throw an exception. It just makes your code lie to you about how many things it's holding, depending on the collection class.
Multi-Field Sorting Without the Boilerplate
Java 8 added factory methods on Comparator that make multi-level sorting clean:
Comparator<Employee> sorted = Comparator .comparing(e -> e.department) .thenComparingInt(e -> e.salary) .thenComparing(e -> e.name); employees.sort(sorted);
For reversed order:
Comparator<Employee> highestPaidFirst = Comparator .comparingInt(Employee::getSalary) .reversed();
Writing a raw anonymous class for Comparator in an interview signals you haven't used Java 8+ much. The chaining API exists; use it.
What the Interviewer Actually Wants to Hear
When an interviewer asks "what's the difference between Comparable and Comparator," they're fishing for three things:
- The structural answer: Comparable is implemented by the class, Comparator is external
- The practical answer: use Comparable for natural order, Comparator when you need multiple orderings or can't modify the class
- The gotcha answer: the integer overflow bug and the "consistent with equals" contract
If they ask you to implement a custom PriorityQueue for a graph problem and you write implements Comparable on your node class, you've demonstrated you actually understand which interface does what. Most candidates just pass a lambda comparator and never say why. Naming the interface and explaining the choice takes five extra seconds and earns points that are surprisingly rare.
The interviewers who grade on "thought process" are specifically looking for exactly this kind of unprompted precision. You practiced knowing the answer; the interview is about whether you can explain it while someone watches you code.
You can practice exactly this kind of narrated explanation at SpaceComplexity, where AI mock interviews push follow-up questions until the reasoning is airtight, not just the answer.