Sitemap
Manish Pandey

Manish Pandey

Software Engineer at Mastercard

Comparators in Java

7 min readSep 19, 2026

--

Comparator is one of the most important Java concepts for Collections, sorting, TreeSet, TreeMap, PriorityQueue and many DSA problems.

1. What is a Comparator?

Suppose you have:

List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 3);

You can naturally sort them:

Collections.sort(numbers);

Result:

[1, 2, 3, 5, 8]

But what if you have objects?

class Employee {
String name;
int salary;
int age;
}

Suppose:

John   30   80000
Alice 25 90000
Bob 35 70000

How does Java know whether you want to sort by:

name?
age?
salary?

You tell Java how to compare two objects using a Comparator.

2. The basic definition

Comparator<T> is a functional interface used to define a custom ordering between two objects.

Comparator<T>

The most important method is:

int compare(T o1, T o2);

The return value has three meanings:

compare(o1, o2)
negative → o1 comes BEFORE o2
zero → o1 and o2 are considered equal for this ordering
positive → o1 comes AFTER o2

For example:

Comparator<Integer> comparator = new Comparator<Integer>() {
    @Override
public int compare(Integer a, Integer b) {
return a - b;
}
};

Conceptually:

a = 2
b = 5
2 - 5 = -3negative
↓
2 comes before 5

3. The simplest Comparator

Let’s sort integers ourselves.

List<Integer> numbers =
new ArrayList<>(Arrays.asList(5, 2, 8, 1, 3));
numbers.sort(new Comparator<Integer>() {    @Override
public int compare(Integer a, Integer b) {
return a - b;
}
});

Result:

[1, 2, 3, 5, 8]

The comparator says:

return a - b;

which means:

a < b → negative
a = b → 0
a > b → positive

4. Descending order

What if we want:

8 5 3 2 1

Use:

return b - a;

Example:

numbers.sort(new Comparator<Integer>() {
    @Override
public int compare(Integer a, Integer b) {
return b - a;
}
});

5. Lambda makes Comparator much shorter

The previous code is verbose.

Because Comparator is a functional interface, we can use a lambda.

Instead of:

numbers.sort(new Comparator<Integer>() {
    @Override
public int compare(Integer a, Integer b) {
return a - b;
}
});

write:

numbers.sort((a, b) -> a - b);

Much cleaner.

Or:

numbers.sort((a, b) -> Integer.compare(a, b));

I recommend learning the second form because it avoids integer overflow problems.

6. Why Integer.compare() is better than a - b

You will frequently see:

(a, b) -> a - b

It works for many normal values, but it can overflow.

For example:

int a = Integer.MAX_VALUE;
int b = -1;

Then:

a - b

can overflow.

Prefer:

(a, b) -> Integer.compare(a, b)

or:

Integer::compare

So:

numbers.sort(Integer::compare);

sorts ascending.

For descending:

numbers.sort((a, b) -> Integer.compare(b, a));

7. Comparator with custom objects

This is where Comparator becomes really useful.

Create:

class Employee {
    String name;
int age;
double salary;
Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
}

Create employees:

List<Employee> employees = new ArrayList<>();
employees.add(new Employee("John", 30, 80000));
employees.add(new Employee("Alice", 25, 90000));
employees.add(new Employee("Bob", 35, 70000));

Now suppose we want to sort by age.

employees.sort((e1, e2) ->
Integer.compare(e1.age, e2.age)
);

Result:

Alice → 25
John → 30
Bob → 35

8. Sort by salary

Simply change the comparison:

employees.sort((e1, e2) ->
Double.compare(e1.salary, e2.salary)
);

Ascending salary:

Bob    → 70000
John → 80000
Alice → 90000

Descending:

employees.sort((e1, e2) ->
Double.compare(e2.salary, e1.salary)
);

Result:

Alice → 90000
John → 80000
Bob → 70000

9. Sort by String

Suppose we want to sort employees alphabetically.

employees.sort((e1, e2) ->
e1.name.compareTo(e2.name)
);

Result:

Alice
Bob
John

Why does compareTo() work?

Because String already implements Comparable.

For example:

"Apple".compareTo("Banana")

returns a negative value.

And:

"Banana".compareTo("Apple")

returns a positive value.

10. Comparator.comparing()

Java provides a very useful utility:

Comparator.comparing()

Instead of:

employees.sort((e1, e2) ->
Integer.compare(e1.age, e2.age)
);

you can write:

employees.sort(
Comparator.comparing(e -> e.age)
);

Even better with method references when fields have getters:

employees.sort(
Comparator.comparing(Employee::getAge)
);

This is extremely common in professional Java code.

11.comparingInt()

For primitive int, Java provides:

Comparator.comparingInt()

Example:

employees.sort(
Comparator.comparingInt(Employee::getAge)
);

For long:

Comparator.comparingLong(...)

For double:

Comparator.comparingDouble(...)

This avoids unnecessary boxing.

12. Descending order using

reversed()

Suppose:

employees.sort(
Comparator.comparingInt(Employee::getAge)
);

Ascending age.

To reverse it:

employees.sort(
Comparator.comparingInt(Employee::getAge)
.reversed()
);

Now:

35
30
25

This is much cleaner than manually reversing the comparison.

13. Multiple sorting conditions

This is one of the most important Comparator concepts.

Suppose employees are:

Alice   age=30
Bob age=25
Charlie age=30
David age=25

You want:

Sort by age first. If age is the same, sort by name.

Use:

employees.sort(
Comparator.comparingInt(Employee::getAge)
.thenComparing(Employee::getName)
);

Think about the logic:

First:
compare age
If age is different:
use age
If age is same:
compare name

Conceptually:

age
↓
same?
├── NO → age decides
│
└── YES
↓
name
↓
decides

14. Multiple conditions with different directions

Suppose we want:

Salary descending, then name ascending.

employees.sort(
Comparator.comparingDouble(Employee::getSalary)
.reversed()
.thenComparing(Employee::getName)
);

Result could be:

Alice   90000
John 80000
Bob 70000

15. Very important:

Comparator vs Comparable

This is a common interview question.

Comparable

You define the object’s natural ordering.

class Employee implements Comparable<Employee>

Then:

@Override
public int compareTo(Employee other) {
return Integer.compare(this.age, other.age);
}

Now Java knows:

The natural ordering of Employee is age.

Comparator

You define an external/custom ordering.

Comparator<Employee> bySalary =
Comparator.comparingDouble(Employee::getSalary);

Now you can have many different orderings:

Employee
|
├── natural order → age
├── Comparator → salary
├── Comparator → name
└── Comparator → salary + name

This is the major difference.

16. Comparable example

class Employee implements Comparable<Employee> {
    String name;
int age;
@Override
public int compareTo(Employee other) {
return Integer.compare(this.age, other.age);
}
}

Now:

Collections.sort(employees);

automatically sorts by age.

Get Manish Pandey’s stories in your inbox

Join Medium for free to get updates from this writer.

Because Employee tells Java:

"My natural order is age."

17. Comparator allows multiple orders

This is why Comparator is powerful.

Comparator<Employee> byAge =
Comparator.comparingInt(Employee::getAge);
Comparator<Employee> byName =
Comparator.comparing(Employee::getName);
Comparator<Employee> bySalary =
Comparator.comparingDouble(Employee::getSalary);

Then:

employees.sort(byAge);

or:

employees.sort(byName);

or:

employees.sort(bySalary);

Same object, different sorting rules.

18. Comparator and PriorityQueue

This is very important for DSA.

Suppose:

PriorityQueue<Integer> pq = new PriorityQueue<>();

By default:

smallest element comes first

But suppose you want the largest element first.

PriorityQueue<Integer> pq =
new PriorityQueue<>(
(a, b) -> Integer.compare(b, a)
);

Now:

pq.offer(10);
pq.offer(50);
pq.offer(20);
System.out.println(pq.poll());

Output:

50

Then:

20
10

So Comparator can define the priority.

19. PriorityQueue with objects

Suppose:

class Employee {
    String name;
int salary;
// constructor/getters
}

You want the employee with the highest salary first.

PriorityQueue<Employee> pq =
new PriorityQueue<>(
Comparator.comparingDouble(Employee::getSalary)
.reversed()
);

Then:

pq.offer(employee1);
pq.offer(employee2);
pq.offer(employee3);

The highest-salary employee is at the head.

This pattern appears frequently in:

  • Top K problems
  • scheduling
  • task prioritization
  • Dijkstra’s algorithm
  • merging K sorted lists
  • heap problems

20. Comparator and TreeSet

Here’s something very important.

You can give a Comparator to a TreeSet.

Set<Integer> numbers =
new TreeSet<>((a, b) -> Integer.compare(b, a));

Now:

numbers.add(10);
numbers.add(30);
numbers.add(20);

Output:

[30, 20, 10]

The Comparator determines the ordering.

21. Comparator and TreeMap

Same idea.

Map<String, Integer> map =
new TreeMap<>(Comparator.reverseOrder());

Now keys are sorted in descending order.

map.put("Apple", 10);
map.put("Banana", 20);
map.put("Orange", 30);

Iteration order:

Orange
Banana
Apple

22. The most important rule about

compare()

Suppose:

Comparator<Integer> comparator =
(a, b) -> Integer.compare(a, b);

Then:

compare(a,b) < 0

means:

a comes before b

And:

compare(a,b) > 0

means:

a comes after b

And:

compare(a,b) == 0

means:

a and b are considered equal by this ordering

Don’t memorize “return -1 or 1.”

The actual contract is based on negative / zero / positive.

23. A common mistake

People often write:

return 1;

or:

return -1;

based on complicated conditions.

You don’t need to.

For integers:

Integer.compare(a, b)

For longs:

Long.compare(a, b)

For doubles:

Double.compare(a, b)

For strings:

a.compareTo(b)

Use these.

24. Null handling

Suppose you have:

List<String> names =
Arrays.asList("John", null, "Alice");

Normally comparing null can cause problems.

Java provides:

Comparator.nullsFirst(...)

Example:

names.sort(
Comparator.nullsFirst(
Comparator.naturalOrder()
)
);

Result:

null
Alice
John

Or:

Comparator.nullsLast(...)

Result:

Alice
John
null

This is more advanced, but useful in real applications.

25. Comparator composition

You can build complex sorting rules.

For example:

Comparator<Employee> comparator =
Comparator.comparingInt(Employee::getAge)
.thenComparing(
Employee::getName
)
.thenComparingDouble(
Employee::getSalary
);

Meaning:

1. age
↓
2. if same → name
↓
3. if same → salary

You can even reverse individual parts.

For example:

Comparator<Employee> comparator =
Comparator.comparingInt(Employee::getAge)
.reversed()
.thenComparing(Employee::getName);

Means:

age descending
name ascending

26. How sorting actually uses Comparator

This is important for understanding the concept.

Suppose:

List<Integer> numbers =
Arrays.asList(5, 2, 8);

And:

numbers.sort(
(a, b) -> Integer.compare(a, b)
);

The sorting algorithm repeatedly asks your Comparator questions like:

"Should 5 come before 2?"
compare(5, 2)
↓
positive
↓
5 comes after 2

Then:

"Should 2 come before 8?"
compare(2, 8)
↓
negative
↓
2 comes before 8

The sorting algorithm uses these results to arrange the elements.

You don’t control which pairs it compares.

You only provide the comparison rule.

27. A very important DSA example

Suppose you have intervals:

[5, 10]
[1, 3]
[2, 6]
[8, 12]

You want to sort by starting point.

Represent:

int[][] intervals = {
{5, 10},
{1, 3},
{2, 6},
{8, 12}
};

Sort:

Arrays.sort(
intervals,
(a, b) -> Integer.compare(a[0], b[0])
);

Result:

[1, 3]
[2, 6]
[5, 10]
[8, 12]

This exact pattern appears in many LeetCode problems.

28. Another common DSA pattern

Sort strings by length:

List<String> words =
Arrays.asList("Java", "I", "Spring", "API");

Comparator:

words.sort(
Comparator.comparingInt(String::length)
);

Result:

I
API
Java
Spring

Descending:

words.sort(
Comparator.comparingInt(String::length)
.reversed()
);

29. Comparator cheat sheet

Keep this:

// Integer ascending
Comparator<Integer> c1 =
Integer::compare;
// Integer descending
Comparator<Integer> c2 =
(a, b) -> Integer.compare(b, a);
// Object by int field
Comparator<Employee> c3 =
Comparator.comparingInt(Employee::getAge);
// Object by double field
Comparator<Employee> c4 =
Comparator.comparingDouble(Employee::getSalary);
// Object by String
Comparator<Employee> c5 =
Comparator.comparing(Employee::getName);
// Descending
Comparator<Employee> c6 =
Comparator.comparingInt(Employee::getAge)
.reversed();
// Multiple fields
Comparator<Employee> c7 =
Comparator.comparingInt(Employee::getAge)
.thenComparing(Employee::getName);

30. The mental model I want you to remember

Don’t think:

“Comparator is something used for sorting.”

Think:

Comparator is an object/function that tells Java how two objects should be ordered relative to each other.

Comparator
|
↓
compare(a, b)
|
+---------+---------+
| | |
negative 0 positive
| | |
↓ ↓ ↓
a before equal a after
b b

And that comparison rule can be used by:

Comparator
|
+── List.sort()
|
+── Collections.sort()
|
+── Arrays.sort()
|
+── PriorityQueue
|
+── TreeSet
|
+── TreeMap

For your DSA learning, master these three levels in order:

Level 1

(a, b) -> Integer.compare(a, b)

Level 2

Comparator.comparingInt(Employee::getAge)

Level 3

Comparator.comparingInt(Employee::getAge)
.thenComparing(Employee::getName)
.reversed();

--

--

Manish Pandey
Manish Pandey