skip to content

Strategy in Java

Strategy in the JDK is Comparator handed to a sort to swap ordering at runtime. Lambdas and functional interfaces made this pattern nearly invisible in modern Java, which is the point interviewers want you to make.

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

questions

4

How is Comparator an example of the Strategy pattern in the JDK, and how do you use it to vary sort order at runtime?

level: juniorimportance: must knowfreq 72%

answer

  1. Comparator = swappable ordering rule = Strategy
  2. compare(a,b) → negative / zero / positive
  3. Sort engine fixed (TimSort), rule passed in
  4. Collections.sort(list, comparator) / list.sort(comparator)
  5. Comparator (external) vs Comparable (natural, built-in)

basics

~20 s

A Comparator is a small object that says how to order two items. You pass different Comparators to Collections.sort or List.sort to get different orderings without changing the sort code. That swappable rule is the Strategy pattern.

solid answer

~40 s

The Strategy pattern lets you pick an algorithm at runtime by passing it in as an object. In the JDK, Comparator is the textbook example: sort methods like Collections.sort(list, comparator) and List.sort(comparator) take a Comparator that defines the ordering rule, while the sort algorithm itself stays fixed. To order people by name you pass one Comparator; to order by age you pass another; the calling code chooses which 'strategy' at runtime. Comparator.compare(a, b) returns negative, zero, or positive, which the sort uses to arrange elements. Since Comparator is a functional interface (one abstract method), you can supply the strategy as a lambda — Comparator.comparing(Person::getAge) — keeping it lightweight. This is why you can sort the same list many different ways without touching the elements or the sort engine.

code

java · 10 lines
java
record Person(String name, int age) {}

List<Person> people = new ArrayList<>(List.of(
    new Person("Ada", 36), new Person("Bob", 28)));

Comparator<Person> byName = Comparator.comparing(Person::name);
Comparator<Person> byAge  = Comparator.comparingInt(Person::age);

people.sort(byName);  // choose ordering strategy at runtime
people.sort(byAge);   // same list, different strategy, same sort engine

go deeper

for a junior

Can state that a Comparator is a rule for ordering two items and that passing different Comparators to sort produces different orderings.

for a middle

Explains the compare contract (sign of the int), distinguishes Comparator from Comparable, and connects 'pass the rule in' to the Strategy pattern.

for a senior

Frames Comparator as Strategy precisely — fixed context (sort engine) vs interchangeable algorithm (ordering) chosen at runtime — and lists the JDK touchpoints (sort, TreeMap/Set, Stream.sorted, PriorityQueue).

for a principal

Discusses why externalizing ordering as a strategy is the right design (open/closed, no element-class edits), the functional-interface synergy enabling lightweight strategies, and trade-offs vs natural ordering and stability guarantees.

## What the Strategy pattern is The **Strategy pattern** is a design pattern where you define a family of interchangeable algorithms, put each one behind a common interface, and let the caller choose which one to use **at runtime**. The code that *uses* the algorithm (the 'context') doesn't hard-code one behavior — it holds a reference to the strategy interface and delegates to whatever concrete strategy was handed to it. Analogy: a navigation app has one 'find a route' button (the context) but several routing strategies — fastest, shortest, avoid tolls. You pick a strategy; the button's code never changes. ## Why sorting needs this Sorting has two separable concerns: 1. **The sort algorithm** — how elements get rearranged (the JDK uses a stable, optimized merge/insertion sort called TimSort). This is fixed and you rarely care about it. 2. **The ordering rule** — given two elements, which comes first? This varies constantly: by name, by age, descending, by last name then first name. If ordering were baked into the sort, you'd need a different sort method per ordering. Instead the JDK factors the ordering rule out into a **strategy object** you pass in. ## Comparator: the strategy interface `java.util.Comparator<T>` is that strategy interface. Its single core method is: ```java int compare(T a, T b); ``` The contract: return a **negative** number if `a` should come before `b`, **zero** if they're equal in ordering, **positive** if `a` should come after `b`. The sort engine calls `compare` repeatedly and uses the sign to arrange elements. The JDK sort entry points accept this strategy: - `Collections.sort(List<T> list, Comparator<? super T> c)` - `list.sort(Comparator<? super T> c)` (on `List`) - `Arrays.sort(T[] a, Comparator<? super T> c)` - It also flows through `TreeMap`, `TreeSet`, `Stream.sorted(comparator)`, `PriorityQueue`, etc. ## Varying order at runtime Because the rule is a parameter, the *same* list can be sorted many ways by passing a different `Comparator`: ```java List<Person> people = ...; people.sort(byName); // one strategy people.sort(byAge); // a different strategy, same sort engine ``` The choice can be made dynamically — e.g. from a user clicking a column header — so it is genuinely a *runtime* selection, the defining trait of Strategy. ## Comparator vs Comparable There are two related JDK pieces: - **`Comparable<T>`** (method `compareTo`) defines an element's **one natural ordering**, built into the class itself (e.g. `String` is alphabetical, `Integer` is numeric). This is more like a fixed default. - **`Comparator<T>`** is an **external, swappable** ordering — the Strategy. You provide as many as you like, none of which require modifying the element class. Sorting without a comparator (`Collections.sort(list)`) uses the elements' natural `Comparable` ordering; sorting *with* a comparator overrides it with your strategy. ## Why it's lightweight in modern Java `Comparator` has exactly one abstract method, so it is a **functional interface** — you can write the strategy as a **lambda** or method reference instead of a whole class: ```java people.sort((a, b) -> a.getName().compareTo(b.getName())); people.sort(Comparator.comparing(Person::getName)); ``` This makes passing a strategy almost free, which is why Strategy-via-Comparator is so pervasive in idiomatic Java.

  • What's the difference between Comparable and Comparator?
    Comparable (compareTo) is the element's single natural ordering, baked into the class. Comparator (compare) is an external, swappable ordering you pass to sort — multiple strategies, no change to the element class.
  • What must compare(a, b) return?
    An int: negative if a comes before b, zero if equal in ordering, positive if a comes after b. Only the sign matters, not the magnitude.

A sort method is like a single 'sort' button on a spreadsheet; the Comparator is the column you tell it to sort by. Same button, different rule each time.

saying these in an interview costs you the question

  • Thinking compare returns a boolean — it returns an int whose sign matters
  • Confusing Comparator (external, many) with Comparable (one natural ordering)
  • Claiming you must modify the element class to change sort order
  • Saying Strategy changes the sort algorithm — it changes the ordering rule, not the engine

context

open as a page

Why do lambdas and functional interfaces make passing a strategy lightweight in Java, compared with the classic Strategy implementation?

level: middleimportance: must knowfreq 64%

basics

~20 s

A functional interface has just one method, so instead of writing a whole class to hold a strategy, you can write it inline as a short lambda. Comparator is functional, so 'sort by age' becomes one line instead of a new class.

open as a page

How do you build a multi-key, mixed-direction ordering using Comparator combinators, and what are the correctness pitfalls?

level: seniorimportance: should knowfreq 55%

basics

~10 s

Chain Comparators: start with Comparator.comparing(key1), add .thenComparing(key2) for tie-breaks, and use .reversed() to flip a direction. Be careful where .reversed() applies and handle nulls explicitly so sorting doesn't throw.

open as a page

When should you design an API to accept a Strategy (like Comparator) versus using inheritance/Template Method, and what are the trade-offs of behavior-as-a-parameter?

level: principalimportance: should knowfreq 40%

basics

~20 s

Use a Strategy (pass behavior in, like a Comparator) when callers need to vary one step at runtime and combine behaviors freely. Use inheritance/Template Method when the variation is a fixed family known at design time. Strategy favors composition over subclassing.

open as a page