Comparable vs Comparator in Java
Both are used for sorting objects, but they serve different purposes. This is one of the most frequently asked Collections interview questions.
Visual Decision Tree: Which One Should I Use?
Use this flowchart to decide between Comparable and Comparator in any situation:
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
A(("Need to sort objects?")) --> B{"Do you own/control\nthe class source code?"}
B -->|Yes| C{"Need multiple\nsort orders?"}
B -->|No| D(["Use Comparator\n(external strategy)"])
C -->|"No, just one\nnatural order"| E(["Use Comparable\n(implement in class)"])
C -->|"Yes, multiple\nways to sort"| F(["Use BOTH!\nComparable for default +\nComparators for alternatives"])
D --> G{"Is it a one-time sort?"}
G -->|Yes| H[/"Lambda / anonymous\nComparator"/]
G -->|No, reusable| I[["Named Comparator class\nor static field"]]
style A fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
style B fill:#D1FAE5,stroke:#6EE7B7,color:#065F46
style C fill:#FEF3C7,stroke:#FCD34D,color:#92400E
style D fill:#FEE2E2,stroke:#FCA5A5,color:#991B1B
style E fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
style F fill:#D1FAE5,stroke:#6EE7B7,color:#065F46
style G fill:#FEF3C7,stroke:#FCD34D,color:#92400E
style H fill:#FEE2E2,stroke:#FCA5A5,color:#991B1B
style I fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
style e fill:#D1FAE5,stroke:#6EE7B7,color:#065F46
style t fill:#FEF3C7,stroke:#FCD34D,color:#92400E Interface Relationship Diagram
See how these two interfaces relate to classes differently:
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
classDiagram
class Comparable~T~ {
<<interface>>
+compareTo(T other) int
}
class Comparator~T~ {
<<interface>>
+compare(T o1, T o2) int
+reversed() Comparator~T~
+thenComparing(Comparator~T~) Comparator~T~
}
class Employee {
-int id
-String name
-double salary
+compareTo(Employee other) int
}
class SalaryComparator {
+compare(Employee o1, Employee o2) int
}
class NameComparator {
+compare(Employee o1, Employee o2) int
}
Comparable~T~ <|.. Employee : implements\n(I define MY OWN order)
Comparator~T~ <|.. SalaryComparator : implements\n(I define order FOR others)
Comparator~T~ <|.. NameComparator : implements\n(I define order FOR others)
Employee ..> SalaryComparator : sorted by
Employee ..> NameComparator : sorted by
style Comparable fill:#D1FAE5,stroke:#6EE7B7,color:#065F46
style Comparator fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
style Employee fill:#FEF2F2,stroke:#FCD34D,color:#1E40AF
style SalaryComparator fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
style NameComparator fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF Sorting Flow: How Collections.sort() Works
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
sequenceDiagram
participant App as Application Code
participant CS as Collections.sort()
participant List as List Elements
participant Comp as Comparator (external)
Note over App,Comp: Path 1: Comparable (Natural Ordering)
App->>CS: Collections.sort(employeeList)
CS->>List: element1.compareTo(element2)
List-->>CS: returns negative/zero/positive
CS->>List: element2.compareTo(element3)
List-->>CS: returns negative/zero/positive
CS-->>App: Sorted list (by natural order)
Note over App,Comp: Path 2: Comparator (Custom Ordering)
App->>CS: Collections.sort(employeeList, bySalary)
CS->>Comp: bySalary.compare(element1, element2)
Comp-->>CS: returns negative/zero/positive
CS->>Comp: bySalary.compare(element2, element3)
Comp-->>CS: returns negative/zero/positive
CS-->>App: Sorted list (by salary) Real-World Analogy
A fun way to remember the difference forever:
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
subgraph comparable_world ["Comparable = I Know My Own Rank"]
direction LR
S1(["Student A\n'I know I'm #2\nbecause my GPA is 3.8'"])
S2(["Student B\n'I know I'm #1\nbecause my GPA is 3.9'"])
S3(["Student C\n'I know I'm #3\nbecause my GPA is 3.5'"])
S1 ~~~ S2 ~~~ S3
end
subgraph comparator_world ["Comparator = A Judge Decides"]
direction LR
J1{{"Judge: Height\n'A is taller than B'"}}
J2{{"Judge: Speed\n'B is faster than A'"}}
J3{{"Judge: Creativity\n'C beats everyone!'"}}
J1 ~~~ J2 ~~~ J3
end
comparable_world -.-|"Self-aware objects\nOne fixed ranking"| NOTE1["Natural Order\njava.lang.Comparable"]
comparator_world -.-|"External judges\nMultiple rankings possible"| NOTE2["Custom Orders\njava.util.Comparator"]
style J1 fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
style J2 fill:#D1FAE5,stroke:#6EE7B7,color:#065F46
style J3 fill:#FEF3C7,stroke:#FCD34D,color:#92400E
style S1 fill:#FEE2E2,stroke:#FCA5A5,color:#991B1B
style S2 fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
style S3 fill:#D1FAE5,stroke:#6EE7B7,color:#065F46 Memory trick for interviews:
- Comparable = "Compar-ABLE" = I am ABLE to compare MYSELF (self-contained)
- Comparator = "Compar-ATOR" = an external operATOR that compares others (third party)
Quick Comparison
| Feature | Comparable | Comparator |
|---|---|---|
| Package | java.lang | java.util |
| Method | compareTo(T o) | compare(T o1, T o2) |
| Where defined | Inside the class itself | External / separate class |
| Sorting logic | One natural ordering per class | Multiple custom orderings |
| Modifies class | Yes (implements interface) | No (external strategy) |
| Use with | Collections.sort(list) | Collections.sort(list, comparator) |
Comparable — Natural Ordering
The class itself defines how its objects should be sorted. One sorting logic per class.
public class Employee implements Comparable<Employee> {
private int id;
private String name;
private double salary;
@Override
public int compareTo(Employee other) {
return Integer.compare(this.id, other.id); // sort by id (natural order)
}
}
List<Employee> employees = getEmployees();
Collections.sort(employees); // uses compareTo() — sorts by id
Return value rules
| Return | Meaning |
|---|---|
Negative (< 0) | this comes before other |
Zero (0) | this and other are equal |
Positive (> 0) | this comes after other |
Classes that implement Comparable
String, Integer, Double, LocalDate, BigDecimal — all have natural ordering built in.
List<String> names = List.of("Charlie", "Alice", "Bob");
Collections.sort(names); // [Alice, Bob, Charlie] — String's natural order
Comparator — Custom / Multiple Orderings
Defined outside the class. You can create many different sorting strategies.
// Sort by name
Comparator<Employee> byName = (e1, e2) -> e1.getName().compareTo(e2.getName());
// Sort by salary (descending)
Comparator<Employee> bySalaryDesc = (e1, e2) -> Double.compare(e2.getSalary(), e1.getSalary());
// Sort by department, then by name within department
Comparator<Employee> byDeptThenName = Comparator
.comparing(Employee::getDepartment)
.thenComparing(Employee::getName);
Collections.sort(employees, byName);
Collections.sort(employees, bySalaryDesc);
Collections.sort(employees, byDeptThenName);
Modern Comparator API (Java 8+)
Java 8 added powerful factory methods to Comparator:
// Simple field comparison
Comparator.comparing(Employee::getSalary)
// Reverse order
Comparator.comparing(Employee::getSalary).reversed()
// Chain comparisons (sort by dept, then salary desc, then name)
Comparator.comparing(Employee::getDepartment)
.thenComparing(Employee::getSalary, Comparator.reverseOrder())
.thenComparing(Employee::getName)
// Null-safe comparison
Comparator.comparing(Employee::getDepartment,
Comparator.nullsLast(Comparator.naturalOrder()))
// With Streams
employees.stream()
.sorted(Comparator.comparing(Employee::getSalary).reversed())
.limit(5)
.collect(Collectors.toList()); // top 5 earners
Common Sorting Patterns
Sort a list of strings by length
List<String> words = List.of("Java", "Go", "Python", "C");
words.stream()
.sorted(Comparator.comparingInt(String::length))
.toList(); // [C, Go, Java, Python]
Sort a map by values
Map<String, Integer> scores = Map.of("Alice", 90, "Bob", 85, "Charlie", 95);
scores.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));
// Charlie: 95, Alice: 90, Bob: 85
Sort with nulls
List<Employee> employees = getEmployees(); // some may have null department
employees.sort(Comparator.comparing(
Employee::getDepartment,
Comparator.nullsLast(Comparator.naturalOrder())
));
TreeSet / TreeMap with custom order
// Sorted set by salary (descending)
TreeSet<Employee> topEarners = new TreeSet<>(
Comparator.comparing(Employee::getSalary).reversed()
);
topEarners.addAll(employees);
Consistency with equals
If compareTo() returns 0 for two objects, equals() should also return true — and vice versa. If they're inconsistent:
- TreeSet / TreeMap use
compareTo()— may treat unequal objects as duplicates - HashSet / HashMap use
equals()+hashCode()— may treat "same" objects as different
// INCONSISTENT — BigDecimal
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("1.00");
a.equals(b); // false (different scale)
a.compareTo(b); // 0 (numerically equal)
new HashSet<>(List.of(a, b)).size(); // 2 (uses equals)
new TreeSet<>(List.of(a, b)).size(); // 1 (uses compareTo)
Interview Questions
1. When should you use Comparable vs Comparator?
Use Comparable when there's one obvious natural ordering for the class (e.g., Employee by ID, Date by chronological order). Use Comparator when you need multiple sort options (by name, by salary, by hire date) or when you don't control the class source code. In practice, implement Comparable for the default sort and use Comparators for alternatives.
2. Why should you use Integer.compare(x, y) instead of x - y in compareTo?
Subtraction can overflow. If x = Integer.MAX_VALUE and y = -1, then x - y overflows to a negative number, giving the wrong result. Integer.compare() handles all cases correctly. Same applies to Long.compare() and Double.compare().
3. How does TreeMap use Comparable/Comparator internally?
TreeMap is a Red-Black Tree. It uses compareTo() (Comparable) or the provided Comparator to determine the position of each key in the tree. Keys are sorted as they're inserted. If neither Comparable nor Comparator is available, put() throws ClassCastException at runtime.