skip to content

Ordering & Comparison

Ordering through Comparable's natural order and Comparator's external order, plus the composition helpers. Interviewers use sorting tasks to check both the contract and the fluent API.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

What is the Comparator interface in Java, and how does it differ from Comparable / natural ordering?

level: juniorimportance: must knowfreq 80%

answer

  1. Comparable = natural order, inside the class, one order
  2. Comparator = external rule, separate object, many orders
  3. compare/compareTo: negative/zero/positive
  4. TreeSet/TreeMap use compare, not equals
  5. avoid a-b subtraction (overflow) -> Integer.compare

basics

~20 s

Comparator is an object that tells Java how to order two items. Comparable is built into a class (its natural order); Comparator is a separate, external rule you pass to sort, so you can sort the same objects many different ways.

solid answer

~40 s

There are two ordering mechanisms in Java. Comparable defines a type's natural ordering via compareTo on the class itself (e.g. String alphabetical, Integer numeric) - one fixed order baked into the type. Comparator is a separate object holding an external ordering rule; its compare(a, b) method returns a negative number if a should come before b, zero if they're equal, positive if a comes after. You pass a Comparator to things like Collections.sort, List.sort, Stream.sorted, or a TreeMap/TreeSet to override or supply ordering. Use Comparator when you don't own the class, want multiple orderings, or need order that isn't 'natural' (e.g. sort people by age, then name). The contract: it must be consistent and ideally consistent with equals for sorted collections.

go deeper

for a junior

Knows Comparator orders two objects via compare returning negative/zero/positive, and that Comparable is the built-in natural order.

for a middle

Explains when to choose Comparator vs Comparable, supplies one to sort/TreeSet, and avoids the subtraction-overflow trap.

for a senior

Articulates the comparator contract (antisymmetry, transitivity), 'consistent with equals', and why TreeSet uses compare not equals.

for a principal

Reasons about contract violations causing sort exceptions, API design tradeoffs of natural vs injected ordering, and stability guarantees of the sort.

## The problem: how does Java know what 'sorted' means? To sort a list, the language needs a rule answering: 'given two elements a and b, which comes first?' Java offers two ways to supply that rule. ### 1. Natural ordering — `Comparable<T>` `Comparable` is an interface a class implements itself, with one method: ```java int compareTo(T other); ``` It returns a **negative** int if `this` is less than `other`, **zero** if equal, **positive** if greater. This is the type's *natural ordering* — one fixed, built-in order. Many JDK types are already Comparable: `String` (lexicographic), `Integer`/`Double` (numeric), `LocalDate` (chronological). Because the rule lives inside the class, there is only ever **one** natural order per type. ### 2. External ordering — `Comparator<T>` `Comparator` is a **separate object** you create that holds an ordering rule. Its core method: ```java int compare(T a, T b); ``` Same sign convention: negative means a comes before b, zero means equal-for-ordering, positive means a comes after b. The key difference is that the rule is **not** inside the class being sorted — it lives outside it. That gives you three powers: - **Sort types you don't own / can't change** (no need to edit the class). - **Multiple different orderings** for the same type (by name, by age, by salary...). - **Orderings that aren't 'natural'** (reverse, by a derived key, custom business rules). ### Where you supply each Methods that sort accept either: ```java List<String> names = ...; names.sort(null); // null Comparator = use natural ordering (Comparable) names.sort(Comparator.naturalOrder()); // same thing, explicit names.sort(Comparator.reverseOrder()); // reversed natural order names.sort((a, b) -> a.length() - b.length()); // custom: by length ``` `Collections.sort(list)` needs the elements to be Comparable; `Collections.sort(list, cmp)` or `list.sort(cmp)` takes a Comparator. `TreeSet`/`TreeMap` use natural ordering unless you pass a Comparator to their constructor. ### The contract (rules a comparator MUST follow) 1. **Antisymmetry/sign consistency:** `sign(compare(a,b)) == -sign(compare(b,a))`. 2. **Transitivity:** if a<b and b<c then a<c. 3. **Substitution:** if `compare(a,b)==0`, they must compare equally against any third element. Violating these can cause `Arrays.sort`/`Collections.sort` to throw `IllegalArgumentException: Comparison method violates its general contract!`. ### 'Consistent with equals' A comparator is *consistent with equals* if `compare(a,b)==0` exactly when `a.equals(b)`. Sorted collections (`TreeSet`, `TreeMap`) use **compare**, not equals, to decide duplicates - so an inconsistent comparator can silently drop elements the collection considers 'equal' for ordering. Aim for consistency, or be aware of the consequence. ### A common bug: subtraction `(a, b) -> a - b` looks clever but **overflows** for large ints (e.g. `Integer.MIN_VALUE - 1`). Prefer `Integer.compare(a, b)` or `Comparator.comparingInt(...)`.

  • Why can `(a,b) -> a - b` be wrong?
    Integer subtraction can overflow when the difference exceeds int range, flipping the sign and breaking the contract. Use Integer.compare(a, b) instead.
  • What does passing null as the Comparator to List.sort do?
    It sorts using the elements' natural ordering (Comparable); it throws ClassCastException if the elements aren't Comparable.

saying these in an interview costs you the question

  • Saying Comparator and Comparable are interchangeable / the same
  • Using a - b subtraction in a comparator (overflows)
  • Thinking sorted collections deduplicate by equals (they use compare)
  • Returning only -1/0/1 is required (any negative/positive works)

context

open as a page

How does Comparator.comparing with a key extractor work, and why is it preferred over writing compare by hand?

level: middleimportance: must knowfreq 75%

basics

~20 s

Comparator.comparing takes a function that pulls a sort key out of each object (like person -> person.getAge()), and builds a comparator that orders objects by that key. It's shorter and less error-prone than writing compare yourself.

open as a page

How do you build a multi-level (tie-breaking) sort using thenComparing, and what determines the order of the chain?

level: middleimportance: must knowfreq 72%

basics

~20 s

thenComparing adds a backup rule used only when the first comparator says two items are equal. You chain them in priority order: first comparator is primary, the next breaks ties, and so on - like sorting by last name, then by first name.

open as a page

How do you sort a collection that may contain null elements (or null sort keys) safely with a Comparator?

level: seniorimportance: should knowfreq 58%

basics

~10 s

Most comparators throw NullPointerException on null. Wrap your comparator with Comparator.nullsFirst(...) or nullsLast(...) so nulls are placed at the start or end instead of crashing.

open as a page

What is the 'Comparison method violates its general contract!' exception, what causes it, and how do you fix it?

level: principalimportance: should knowfreq 42%

basics

~20 s

It's an IllegalArgumentException Java throws when your comparator gives inconsistent answers - for example saying a < b, b < c, but a > c. The fix is to make the comparator's logic consistent (transitive and sign-symmetric).

open as a page