Back to Stream: Sorting & Comparator
Q19Question 19 of 23

Sort employees by age, and for the same age, sort by name

Key Concepts
  • comparingInt(Employee::getAge) avoids boxing each age into an Integer, unlike comparing()
  • thenComparing(Employee::getName) resolves ties when ages are equal
  • comparingInt is preferred for primitive int keys for better performance on hot paths
  • Natural String order is case-sensitive; use CASE_INSENSITIVE_ORDER for mixed-case names
  • The composed comparator is built once and reused for every pair comparison during the sort
  • sorted() is stable: employees identical on both age and name keep their input order

Definition

`comparingInt(age).thenComparing(name)` sorts by age, then breaks age-ties alphabetically.
  • comparingInt(Employee::getAge) extracts the primitive int age with no boxing.
  • thenComparing(Employee::getName) resolves ties when ages are equal, using natural String order.
  • Performance: comparingInt avoids autoboxing each age into an Integer.
  • Case sensitivity: natural String order is case-sensitive; add CASE_INSENSITIVE_ORDER if needed.
  • Stability: identical records keep their original relative order.

Priya at the National Bank of India wants the staff birthday list sorted youngest to oldest, and when two colleagues are the same age she wants them in alphabetical order so the list is predictable.

Understanding the Concepts

How do you sort by age then by name?

JAVA
package com.bank.hr;

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

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

        List<Employee> sorted = staff.stream()
            .sorted(Comparator.comparingInt(Employee::getAge)
                .thenComparing(Employee::getName))
            .collect(Collectors.toList());

        sorted.forEach(e -> System.out.printf("%s (age %d)%n",
            e.getName(), e.getAge()));
    }
}
TEXT
Kavita (age 28)
Amit (age 30)
Priya (age 30)
Rajesh (age 35)

Amit and Priya are both 30, so the name tie-breaker puts Amit before Priya.

Why use comparingInt instead of comparing?

| Method | Key type | Boxing? | Performance |
|---|---|---|---|
| comparingInt(Employee::getAge) | primitive int | No | Better |
| comparing(Employee::getAge) | Integer (boxed) | Yes | Slower on hot paths |

JAVA
// Preferred: no boxing
Comparator.comparingInt(Employee::getAge)

// Avoid: autoboxes each int to Integer
Comparator.comparing(Employee::getAge)

What about case-insensitive name tie-breaking?

JAVA
Comparator<Employee> comp = Comparator
    .comparingInt(Employee::getAge)
    .thenComparing(Employee::getName, String.CASE_INSENSITIVE_ORDER);

What are common mistakes?

Mistake: using comparing instead of comparingInt for a primitive int field.

JAVA
// Less efficient: boxes every age
Comparator.comparing(Employee::getAge)
    .thenComparing(Employee::getName);

// Better: no boxing
Comparator.comparingInt(Employee::getAge)
    .thenComparing(Employee::getName);

What about descending age with ascending name?

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

What about three-level tie-breaking?

JAVA
// Age ASC, then name ASC, then department ASC
Comparator<Employee> threeLevel = Comparator
    .comparingInt(Employee::getAge)
    .thenComparing(Employee::getName)
    .thenComparing(Employee::getDepartment);

What should a senior engineer know?

  • comparingInt returns a specialised comparator that avoids boxing.
  • The composed comparator is built once and applied to every pair, so the composition cost is negligible.
  • Natural String order is case-sensitive; if names come from inconsistent imports, plug in CASE_INSENSITIVE_ORDER.
  • sorted is stable, so employees identical on both age and name keep their input order.

What are common interview follow-up questions?

  • "When is comparingInt better than comparing?" When the key is a primitive int, to avoid boxing.
  • "How do you reverse only the age?" Call .reversed() after comparingInt(age) before thenComparing.
  • "Is the sort stable?" Yes, equal elements keep their input order.

Visual Overview

DIAGRAM
  Before sort:
  [Rajesh/35, Priya/30, Amit/30, Kavita/28]
       │
       │  sorted(comparingInt(age).thenComparing(name))
       │
       │  Primary: age ascending
       │  Kavita(28) < Amit(30) = Priya(30) < Rajesh(35)
       │
       │  Tie-break: Amit vs Priya (both 30) → name ASC
       │  "Amit" < "Priya" → Amit first
       ▼
  [Kavita/28, Amit/30, Priya/30, Rajesh/35]

Key Takeaway

To sort by age and then by name I chain comparators: comparingInt(Employee::getAge) for the primary key, and thenComparing(Employee::getName) for the tie-breaker. I deliberately use comparingInt rather than comparing because age is a primitive int and comparingInt avoids boxing.

In my banking project at the National Bank of India this drove a wellness check-up roster that grouped staff youngest to oldest, with same-age employees listed alphabetically.

The sort is stable, so employees identical on both age and name keep their input order. If names come from inconsistent imports I plug in String.CASE_INSENSITIVE_ORDER as the tie-breaker comparator.

Remember one sentence: comparingInt(age).thenComparing(name) sorts by age without boxing, with alphabetical name as the deterministic tie-breaker.

---

Share
Use ← → arrow keys to navigate