Back to Stream: Sorting & Comparator
Q18Question 18 of 23

Sort employees by department first, then by salary descending within each department

Key Concepts
  • Comparator.comparing(dept).thenComparing(salary, reverseOrder()) gives multi-key sort
  • thenComparing is only consulted when the primary key says two elements are equal
  • Reverse only the salary sub-comparator, not the whole chain, to keep department ascending
  • comparing(salary, Comparator.reverseOrder()) is cleaner than comparing(salary).reversed() for inline reversal
  • sorted() is stable: employees with same department and salary keep their original order
  • The source list is never mutated; collect produces a new sorted list

Definition

`comparing(dept).thenComparing(salary, reverseOrder())` means sort by department, then break ties by salary highest-first.
  • Multi-key sort: Comparator.comparing(primary).thenComparing(secondary) applies a secondary rule when the primary is equal.
  • Selective reversal: reverse only the salary sub-comparator, not the department.
  • thenComparing(key, Comparator.reverseOrder()) is the cleanest inline reversal.
  • Stable sort: equal-key employees keep their original relative order.
  • Non-mutating: the source list is unchanged.

At the National Bank of India, Priya from HR wants the staff register grouped by department in alphabetical order, and inside each department the highest-paid person should appear first. Two rules, applied in order.

Understanding the Concepts

How do you build a multi-key comparator?

JAVA
package com.bank.hr;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

public class MultiKeySort {
    public static void main(String[] args) {
        List<Employee> staff = List.of(
            new Employee("Rajesh",  "Loans",   new BigDecimal("95000")),
            new Employee("Kavita",  "Loans",   new BigDecimal("78000")),
            new Employee("Priya",   "IT",      new BigDecimal("120000")),
            new Employee("Suresh",  "IT",      new BigDecimal("110000")),
            new Employee("Amit",    "Finance", new BigDecimal("85000")));

        List<Employee> sorted = staff.stream()
            .sorted(Comparator.comparing(Employee::getDepartment)
                .thenComparing(Employee::getSalary, Comparator.reverseOrder()))
            .collect(Collectors.toList());

        sorted.forEach(e -> System.out.printf("%-8s %-10s Rs. %s%n",
            e.getDepartment(), e.getName(), e.getSalary().toPlainString()));
    }
}
TEXT
Finance  Amit       Rs. 85000
IT       Priya      Rs. 120000
IT       Suresh     Rs. 110000
Loans    Rajesh     Rs. 95000
Loans    Kavita     Rs. 78000

How does thenComparing work?

thenComparing adds a second rule consulted only when the first rule says two elements are equal. For employees in the same department (primary key equal), the salary comparison (secondary key) decides their relative order.

| Comparison | Department equal? | Result decided by |
|---|---|---|
| Amit vs Priya | Finance != IT | Department (Finance first) |
| Priya vs Suresh | IT == IT | Salary (120000 > 110000: Priya first) |
| Rajesh vs Kavita | Loans == Loans | Salary (95000 > 78000: Rajesh first) |

What are common mistakes?

Mistake: reversing the entire chain instead of just the salary.

JAVA
// WRONG: reverses both department AND salary
Comparator.comparing(Employee::getDepartment)
    .thenComparing(Employee::getSalary)
    .reversed();  // Loans before IT, and low salary first!

// CORRECT: reverse only the salary
Comparator.comparing(Employee::getDepartment)
    .thenComparing(Employee::getSalary, Comparator.reverseOrder());

What about three-key sorting?

JAVA
// Department ASC, then salary DESC, then name ASC
Comparator<Employee> threeKey = Comparator
    .comparing(Employee::getDepartment)
    .thenComparing(Employee::getSalary, Comparator.reverseOrder())
    .thenComparing(Employee::getName);

What about using a separate Comparator variable?

For readability, extract the comparator into a named variable.

JAVA
Comparator<Employee> byDeptThenSalary = Comparator
    .comparing(Employee::getDepartment)
    .thenComparing(Employee::getSalary, Comparator.reverseOrder());

List<Employee> sorted = staff.stream()
    .sorted(byDeptThenSalary)
    .collect(Collectors.toList());

What should a senior engineer know?

  • sorted is stable, so employees who tie on both keys keep their original relative order.
  • sorted returns a brand new stream and never mutates the source list.
  • The Comparator chain is built once and reused for every pair comparison during the sort.
  • For very large lists, the O(n log n) sort with buffering dominates the stream pipeline cost.

What are common interview follow-up questions?

  • "How do you add a third sort key?" Chain another .thenComparing().
  • "Does the position of reversed() matter?" Yes, it affects which keys are reversed.
  • "Is the sort stable?" Yes, equal elements keep their input order.

Visual Overview

DIAGRAM
  Comparator chain:
  ┌──────────────────────────────────────────────┐
  │  1. comparing(department)    → ASC            │
  │  2. thenComparing(salary, reverseOrder) → DESC│
  └──────────────────────────────────────────────┘

  Result:
  Finance  Amit       85000
  IT       Priya      120000     ← salary DESC within IT
  IT       Suresh     110000
  Loans    Rajesh     95000      ← salary DESC within Loans
  Loans    Kavita     78000

Key Takeaway

A multi-key sort chains comparators. I start with Comparator.comparing for the primary key (department ascending) and add thenComparing for the secondary key (salary descending). The critical detail is scoping the reverse: I reversed only the salary sub-comparator using Comparator.reverseOrder(), not the whole chain, which would have flipped the department order too.

In my banking project at the National Bank of India this was the monthly compensation register. HR needed departments grouped alphabetically and, inside each department, the highest earners first.

sorted is stable, so employees who tie on both keys keep their original order, and it never mutates the source list.

Remember one sentence: comparing(dept).thenComparing(salary, reverseOrder()) sorts by department ascending then salary descending, and the reversal is scoped only to the salary key.

---

Share
Use ← → arrow keys to navigate