Comparators in Java
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 70000How 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 o2For example:
Comparator<Integer> comparator = new Comparator<Integer>() { @Override
public int compare(Integer a, Integer b) {
return a - b;
}
};Conceptually:
a = 2
b = 52 - 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 → positive4. Descending order
What if we want:
8 5 3 2 1Use:
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 - bIt works for many normal values, but it can overflow.
For example:
int a = Integer.MAX_VALUE;
int b = -1;Then:
a - bcan overflow.
Prefer:
(a, b) -> Integer.compare(a, b)or:
Integer::compareSo:
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 → 358. Sort by salary
Simply change the comparison:
employees.sort((e1, e2) ->
Double.compare(e1.salary, e2.salary)
);Ascending salary:
Bob → 70000
John → 80000
Alice → 90000Descending:
employees.sort((e1, e2) ->
Double.compare(e2.salary, e1.salary)
);Result:
Alice → 90000
John → 80000
Bob → 700009. Sort by String
Suppose we want to sort employees alphabetically.
employees.sort((e1, e2) ->
e1.name.compareTo(e2.name)
);Result:
Alice
Bob
JohnWhy 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
25This 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=25You 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 ageIf age is different:
use ageIf age is same:
compare name
Conceptually:
age
↓
same?
├── NO → age decides
│
└── YES
↓
name
↓
decides14. 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 7000015. 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 + nameThis 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 firstBut 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:
50Then:
20
10So 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
Apple22. The most important rule about
compare()
Suppose:
Comparator<Integer> comparator =
(a, b) -> Integer.compare(a, b);Then:
compare(a,b) < 0means:
a comes before bAnd:
compare(a,b) > 0means:
a comes after bAnd:
compare(a,b) == 0means:
a and b are considered equal by this orderingDon’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
JohnOr:
Comparator.nullsLast(...)Result:
Alice
John
nullThis 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 → salaryYou can even reverse individual parts.
For example:
Comparator<Employee> comparator =
Comparator.comparingInt(Employee::getAge)
.reversed()
.thenComparing(Employee::getName);Means:
age descending
name ascending26. 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 2Then:
"Should 2 come before 8?"compare(2, 8)
↓
negative
↓
2 comes before 8The 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
SpringDescending:
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 bAnd that comparison rule can be used by:
Comparator
|
+── List.sort()
|
+── Collections.sort()
|
+── Arrays.sort()
|
+── PriorityQueue
|
+── TreeSet
|
+── TreeMapFor 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();






