JAVA • ALGORITHMS AND COMPLEXITY

Writing Comparators — Write a Comparator for custom sorting (intro)

Learn how to define your own sorting rules so Java sorts objects exactly the way you want.

Historical Context & Motivation

Sorting is one of the oldest and most studied problems in computer science. Early computers in the 1950s needed efficient ways to arrange data — payroll records, census information, and scientific measurements. But here's the catch: those early sort routines could only sort numbers or simple text in one fixed order. What if you wanted to sort employees by salary instead of by name, or sort students by GPA from highest to lowest? You'd have to rewrite the entire sorting algorithm each time. The idea of separating the comparison logic from the sorting algorithm itself was a game-changer. Instead of building a new sort for every situation, programmers could write one general-purpose sort and plug in different comparison rules. Java formalized this idea with the Comparator interface, which lets you define exactly how two objects should be compared without touching the sorting code.

1959
Early Sorting Algorithms
Donald Shell publishes ShellSort. Sorting algorithms at this time have their comparison rules baked directly into the code, making them inflexible for different data types.
1970s
Function Pointers in C
The C language introduces qsort(), which accepts a comparison function as a parameter. This is the first widely used approach to separating sorting from comparison logic.
1998
Java 2 Collections Framework
Java 1.2 introduces the java.util.Comparator interface as part of the Collections Framework. Developers can now write reusable, interchangeable sorting rules for any object type.
2014
Lambda Expressions in Java 8
Java 8 adds lambda expressions, making Comparators much shorter to write. Methods like Comparator.comparing() are also introduced for even more concise code.

So why should you care? Imagine you have a list of Student objects. Sometimes you want them sorted alphabetically by name, other times by grade, and occasionally by age. Without Comparators, you'd need to write a separate sorting method for each case. With a Comparator, you write one sorting call and swap in different comparison rules whenever you need a different order. That's the core question this lesson answers: how do you tell Java's built-in sort exactly how to compare your custom objects?

Core Principles & Definitions

A Comparator is a Java interface that defines a single method: compare(T o1, T o2). This method receives two objects and returns an integer that tells the sorting algorithm which one should come first. The return value follows a simple convention: a negative number means o1 comes before o2, zero means they are considered equal, and a positive number means o1 comes after o2.

1

The Comparator Interface

A functional interface in java.util with one abstract method: int compare(T o1, T o2). You implement this interface to define your own ordering rule.
2

Return Value Contract

Return a negative int if o1 < o2, zero if o1 == o2, and a positive int if o1 > o2 according to your custom rule.
3

Comparator vs. Comparable

Comparable is built into the class itself ("natural ordering"). Comparator is external — you can create multiple Comparators for the same class without modifying it.
4

Plugging In a Comparator

Pass your Comparator to Collections.sort(list, comparator) or Arrays.sort(array, comparator). The sort uses your compare() method to decide order.
✦ KEY TAKEAWAY
Think of a Comparator like a judge at a talent show. The contestants (objects) don't decide their own ranking — the judge (Comparator) looks at two contestants at a time and declares which one ranks higher. You can swap in different judges who use different criteria: one ranks by singing ability, another by stage presence. The contestants themselves don't change — only the judging rules do.

Visual Explanation — How a Comparator Fits into Sorting

The unsorted list (left) is passed to Collections.sort() along with your custom Comparator (pink box). The algorithm repeatedly calls compare() on pairs of elements. Your Comparator decides the order; the result is the sorted list (right) arranged by grade ascending.

Notice in the diagram that the sorting algorithm itself (TimSort, which Java uses internally) never changes. The pink box at the bottom — your Comparator — is the only part you write. When the algorithm needs to decide if Alice should go before Bob, it calls compare(alice, bob). Your code returns a number, and the algorithm uses that number to place them in the correct relative position. This clean separation means you can reuse the same sorting machinery with completely different ordering strategies.

How It Works — The compare() Method in Detail

Let's look at the mechanics of writing a Comparator. The Comparator<T> interface is generic — the T represents whatever type of object you're sorting. When you implement it, you replace T with your actual class name. For example, Comparator<Student> means "a comparison rule for Student objects."

The Method Signature

COMPARE METHOD SIGNATURE
int compare(T o1, T o2)
T = the type of objects being compared; o1 = first object; o2 = second object; returns an int (negative, zero, or positive)

Three Ways to Write a Comparator

There are three common syntaxes for creating a Comparator in Java, each progressively shorter. The first is a named class that implements the Comparator interface. This is the most verbose but easiest to read when you're starting out. You create a separate class, implement compare(), and then pass an instance of that class to the sort method.

NAMED CLASS APPROACH
class GradeComparator implements Comparator<Student> { public int compare(Student s1, Student s2) { return s1.grade − s2.grade; } }
Subtracting s2.grade from s1.grade produces a negative number when s1's grade is lower, zero when equal, and positive when s1's grade is higher — exactly what compare() requires for ascending order.

The second approach uses an anonymous class — you define the Comparator inline right where you need it, without giving it a name. The third and most concise approach uses a lambda expression (introduced in Java 8), which boils the whole thing down to a single line: (s1, s2) -> s1.grade - s2.grade. All three approaches do the same thing — they just differ in how much typing you need.

LAMBDA EXPRESSION APPROACH
Collections.sort(students, (s1, s2) -> s1.grade − s2.grade);
The lambda (s1, s2) -> s1.grade - s2.grade is shorthand for a Comparator that sorts students by grade in ascending order. Java infers the types from context.
💡 Ascending vs. Descending
To sort in ascending order, return o1.value − o2.value. To sort in descending order, simply reverse it: o2.value − o1.value. Swapping the order of the subtraction flips the sign of the result, which flips the sort direction.

Three Syntax Styles Compared

Understanding the three ways to write a Comparator is important because you'll encounter all of them when reading Java code. Let's compare them side by side, then visualize how the compare() return values actually drive the sort.

Top row: The same Comparator written three ways — named class, anonymous class, and lambda. Bottom: the return value spectrum shows how negative, zero, and positive results control element ordering. The example shows compare(Alice:88, Bob:95) returning −7, so Alice goes before Bob.
Comparator syntax styles at a glance
Syntax StyleLines of CodeWhen to Use
Named Class~8 linesWhen you need to reuse the same Comparator in multiple places or the logic is complex.
Anonymous Class~6 linesWhen you need the Comparator only once and don't want a separate class file.
Lambda Expression1 lineWhen the comparison logic is simple. This is the most common style in modern Java (8+).

Worked Example — Sorting Students by Grade

Let's walk through a complete example. Suppose you have a Student class with fields name (a String) and grade (an int). You want to sort a list of students by grade from lowest to highest.

Sorting Students by Grade (Ascending)
1
Step 1 — Define the Student ClassCreate a simple class with two fields. For this example, assume the class has a constructor Student(String name, int grade) and a toString() method that prints both fields.
class Student { String name; int grade; }
2
Step 2 — Create Sample DataBuild an ArrayList<Student> and add four students: Alice (88), Bob (95), Carol (72), and Dave (91). The list starts in the order they were added.
List<Student> students = new ArrayList<>(); containing [Alice:88, Bob:95, Carol:72, Dave:91]
3
Step 3 — Write the ComparatorCreate a Comparator using a lambda expression. To sort ascending by grade, subtract the second student's grade from the first: (s1, s2) -> s1.grade - s2.grade. When s1.grade is smaller (e.g., 72 − 88 = −16), the result is negative, so s1 goes first. That's exactly ascending order.
Comparator<Student> byGrade = (s1, s2) -> s1.grade - s2.grade;
4
Step 4 — Sort the ListPass the list and the Comparator to Collections.sort(). Java's sorting algorithm (TimSort) will call your compare() method internally, comparing students pairwise until the list is fully sorted.
Collections.sort(students, byGrade);
5
Step 5 — Verify the ResultPrint the list. The students should now be in ascending order by grade. Carol (72) comes first, then Alice (88), Dave (91), and finally Bob (95).
Output: [Carol:72, Alice:88, Dave:91, Bob:95]
🔄 Want Descending Order?
Just swap the subtraction: (s1, s2) -> s2.grade - s1.grade. Now a higher grade produces a negative result, placing that student first. The output would be [Bob:95, Dave:91, Alice:88, Carol:72].

Comparator vs. Comparable — When to Use Which

Java gives you two interfaces for defining sort order: Comparable and Comparator. They solve related but different problems. Comparable is implemented inside the class whose objects are being sorted, defining the class's "natural order" — the one default way objects of that type should be ranked. Comparator is implemented outside the class, meaning you can create as many different Comparators as you want without ever changing the original class.

Comparable vs. Comparator at a glance
FeatureComparableComparator
Interface LocationImplemented by the class itselfImplemented in a separate class or lambda
Method NamecompareTo(T other)compare(T o1, T o2)
Number of OrderingsOne (the natural order)Unlimited (create a new Comparator for each)
Modifies Original Class?Yes — you must add code to the classNo — completely external
Packagejava.langjava.util
Best ForWhen there's one obvious default sort (e.g., Strings alphabetically)When you need multiple sort orders or can't modify the class
✦ KEY TAKEAWAY
Think of Comparable as a person's default ID badge — it comes built-in and says "sort me this way." A Comparator is like a stack of different lenses you can hold up: one lens sorts by height, another by weight, another by last name. You don't alter the person — you just change the lens you're looking through. Use Comparable for the one obvious order; use Comparator for everything else.

Connection to Advanced Topics

The basic Comparator you've learned is just the beginning. Java 8 introduced powerful utility methods on the Comparator interface that let you chain and compose Comparators in elegant ways. Once you're comfortable writing a simple compare() method, you'll be ready to explore these more advanced patterns.

From basics to advanced Comparator usage
What You Know NowWhat Comes Next
Write a Comparator with a lambda: (a,b) -> a.x - b.xUse Comparator.comparing(Student::getGrade) for cleaner syntax with method references
Sort by one field at a timeChain sorts with .thenComparing() — e.g., sort by grade, then by name for ties
Reverse order manually by swapping o1 and o2Use .reversed() to flip any Comparator's order
Sort simple lists with Collections.sort()Use Comparators in TreeSet, PriorityQueue, and Stream.sorted()
Handle primitive fields like intHandle null values safely with Comparator.nullsFirst() and nullsLast()

Comparators are also foundational to understanding how data structures like priority queues and balanced binary search trees work. These structures need to compare elements constantly to maintain their order, and they accept a Comparator to determine that order. Mastering Comparators now gives you a solid foundation for these more complex topics in algorithms and data structures.

Practice Problems

PROBLEM 1 — CONCEPTUAL
A Comparator's compare(o1, o2) method returns −3. What does this tell the sorting algorithm about the relationship between o1 and o2? Does the specific value −3 matter, or only the fact that it's negative?
PROBLEM 2 — BASIC CALCULATION
You have a Book class with a field int pages. Write a lambda expression Comparator that sorts books in ascending order by page count. Then trace what compare() returns when comparing a 200-page book to a 350-page book.
PROBLEM 3 — INTERMEDIATE
You have a Player class with fields String name and int score. Write a Comparator that sorts players by score in descending order (highest score first). If two players have the same score, sort them alphabetically by name (ascending). Hint: for Strings, you can use s1.compareTo(s2) which returns a negative/zero/positive int just like compare().
PROBLEM 4 — APPLIED
A teacher stores test results as a List<Student> where each Student has name, grade (0–100), and lastName. She wants three different sorted views: (1) by grade descending for a "top performers" list, (2) by last name alphabetically for the gradebook, and (3) by grade ascending for identifying students who need help. Explain how Comparators let her do this without modifying the Student class and write the three lambdas.
PROBLEM 5 — CRITICAL THINKING
A student writes the following Comparator to sort integers in ascending order: (a, b) -> a - b. This works for most inputs, but it can produce incorrect results in rare cases. Can you think of a scenario where a - b gives the wrong answer? (Hint: think about very large positive and very large negative integers.) What safer alternative could you use?

Lesson Summary

A Comparator is a Java interface that separates comparison logic from the sorting algorithm. You implement its compare(T o1, T o2) method, which returns a negative integer (o1 first), zero (equal), or positive integer (o2 first). You can write Comparators as named classes, anonymous classes, or concise lambda expressions. Pass your Comparator to Collections.sort() or Arrays.sort() to sort objects in any custom order.

Unlike Comparable, which defines one natural order inside a class, a Comparator is external — you can create multiple Comparators for the same class without modifying it. To sort in ascending order, return o1.field − o2.field; for descending order, reverse the subtraction. For production code, prefer Integer.compare() to avoid integer overflow issues. Mastering Comparators prepares you for advanced topics like chained sorts, priority queues, and tree-based data structures.

Varsity Tutors • Java • Writing Comparators — Write a Comparator for custom sorting (intro)