Back to Stream Practice: Employee Analysis
Q6Question 6 of 43

Highest, second-highest, Nth-highest salary: top 3 earners

Key Concepts
  • map(getSalary).max(naturalOrder()) returns Optional<BigDecimal> for the highest salary
  • distinct() is critical for Nth-highest: without it, duplicate salary amounts count as separate positions
  • sorted(reverseOrder()).skip(n-1).findFirst() is the pattern for Nth-highest unique salary
  • sorted(comparing(salary).reversed()).limit(3) returns the top 3 earners as Employee objects
  • Top N earners (Employee objects) and Nth-highest salary (BigDecimal amount) are different questions
  • For per-department top earners, group first, then sort and limit within each group

Definition

Use map(getSalary).max(naturalOrder()) for the highest salary, sorted(reverseOrder()).distinct().skip(n-1).findFirst() for the Nth-highest unique salary, and sorted(comparing(salary).reversed()).limit(3) for the top 3 earners.
  • Highest salary: stream().map(Employee::getSalary).max(Comparator.naturalOrder()) returns Optional.
  • Nth-highest unique salary: map(getSalary).distinct().sorted(reverseOrder()).skip(n-1).findFirst() skips the first n-1 unique salary values.
  • distinct() is critical: if two employees earn the same highest salary, without distinct, skip(1) returns the duplicate, not the actual second-highest amount.
  • Top N earners: sorted(comparing(Employee::getSalary).reversed()).limit(n) returns Employee objects, not just salary amounts.
  • Per-department top earner: groupingBy(department, collectingAndThen(maxBy(comparing(salary)), Optional::orElseThrow)).

The National Bank of India compensation committee asks three questions. "What is the highest salary?" is finding the tallest stack of coins on the table. "What is the second-highest?" means removing duplicate stacks first (if two stacks are both 18 coins tall, that is one height), then finding the next tallest. "Who are the top 3 earners?" means lining up all employees by salary and picking the first 3 people, even if two of them earn the same amount.

Understanding the Concepts

What does this question ask?

The interviewer wants you to demonstrate three related but different stream patterns: finding the maximum value, finding the Nth value in a sorted unique sequence, and selecting the top N elements. The critical subtlety is the difference between unique salary amounts and employee objects.

How do you find the highest salary?

JAVA
package com.bank.hr;

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

public class HighestSalary {
    public static void main(String[] args) {
        List<Employee> employees = Employee.getSampleEmployees();

        Optional<BigDecimal> highest = employees.stream()
            .map(Employee::getSalary)
            .max(Comparator.naturalOrder());

        highest.ifPresent(s ->
            System.out.println("Highest salary: Rs." + s));
    }
}
TEXT
Highest salary: Rs.1800000

The map step transforms Stream into Stream. The max(naturalOrder()) compares BigDecimal values using their natural ordering.

How do you find the second-highest salary?

The key is distinct() to remove duplicate salary amounts before skipping.

JAVA
package com.bank.hr;

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

public class SecondHighestSalary {
    public static void main(String[] args) {
        List<Employee> employees = Employee.getSampleEmployees();

        Optional<BigDecimal> secondHighest = employees.stream()
            .map(Employee::getSalary)
            .distinct()
            .sorted(Comparator.reverseOrder())
            .skip(1)
            .findFirst();

        secondHighest.ifPresent(s ->
            System.out.println("Second-highest salary: Rs." + s));
    }
}
TEXT
Second-highest salary: Rs.1500000

Without distinct(), if Rajesh and Amit both earn Rs.1800000, the sorted stream has two Rs.1800000 values. Skipping one gives you Rs.1800000 again, not the actual second-highest.

How do you find the Nth-highest salary?

Generalize the second-highest pattern with skip(n - 1) instead of skip(1).

JAVA
package com.bank.hr;

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

public class NthHighestSalary {
    public static void main(String[] args) {
        List<Employee> employees = Employee.getSampleEmployees();

        int n = 3;
        Optional<BigDecimal> nthHighest = employees.stream()
            .map(Employee::getSalary)
            .distinct()
            .sorted(Comparator.reverseOrder())
            .skip(n - 1)
            .findFirst();

        nthHighest.ifPresent(s ->
            System.out.println("Salary at position " + n + ": Rs." + s));
    }
}
TEXT
Salary at position 3: Rs.1200000

If n exceeds the number of distinct salaries, findFirst() returns Optional.empty().

How do you find the top 3 earners?

Top N earners returns Employee objects, not salary amounts. Two employees earning the same salary are two separate earners.

JAVA
package com.bank.hr;

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

public class Top3Earners {
    public static void main(String[] args) {
        List<Employee> employees = Employee.getSampleEmployees();

        List<Employee> top3 = employees.stream()
            .sorted(Comparator.comparing(Employee::getSalary).reversed())
            .limit(3)
            .toList();

        top3.forEach(e ->
            System.out.println(e.getName() + " (SAV" + e.getAccountId()
                + "): Rs." + e.getSalary()));
    }
}
TEXT
Rajesh (SAV2001): Rs.1800000
Deepak (SAV2008): Rs.1500000
Amit (SAV2004): Rs.1400000

No distinct() here because you want employees, not unique salary amounts.

How is "Nth-highest salary" different from "top N earners"?

| Question | Returns | Uses distinct | Example result |
|---|---|---|---|
| Highest salary | BigDecimal | Not needed | Rs.1800000 |
| Nth-highest salary | BigDecimal | Yes | Rs.1200000 (3rd) |
| Top N earners | List | No | [Rajesh, Deepak, Amit] |
| Employee with Nth salary | Employee | Yes (on salary) | Employee earning the 3rd-highest amount |

JAVA
// Nth-highest SALARY (amount)
Optional<BigDecimal> nthSalary = employees.stream()
    .map(Employee::getSalary)
    .distinct()
    .sorted(Comparator.reverseOrder())
    .skip(n - 1)
    .findFirst();

// Top N EARNERS (employees)
List<Employee> topN = employees.stream()
    .sorted(Comparator.comparing(Employee::getSalary).reversed())
    .limit(n)
    .toList();

What are common mistakes?

Mistake 1: forgetting distinct for Nth-highest salary.

JAVA
// WRONG: two employees at Rs.1800000 counted separately
Optional<BigDecimal> wrong = employees.stream()
    .map(Employee::getSalary)
    .sorted(Comparator.reverseOrder())
    .skip(1)             // skips one Rs.1800000, gets the other Rs.1800000
    .findFirst();        // returns Rs.1800000, NOT the second-highest!

// CORRECT: distinct removes duplicate amounts
Optional<BigDecimal> correct = employees.stream()
    .map(Employee::getSalary)
    .distinct()          // Rs.1800000 appears only once
    .sorted(Comparator.reverseOrder())
    .skip(1)
    .findFirst();        // returns Rs.1500000 (actual second-highest)

Mistake 2: using limit instead of skip+findFirst for Nth.

JAVA
// WRONG: limit(n) returns the first N elements, not the Nth
List<BigDecimal> wrong = employees.stream()
    .map(Employee::getSalary)
    .distinct()
    .sorted(Comparator.reverseOrder())
    .limit(3)            // returns [1800000, 1500000, 1200000]
    .toList();
// You still need to pick the last element

// CORRECT: skip(n-1) + findFirst gets exactly the Nth
Optional<BigDecimal> correct = employees.stream()
    .map(Employee::getSalary)
    .distinct()
    .sorted(Comparator.reverseOrder())
    .skip(2)             // skip first 2
    .findFirst();        // get the 3rd

How do you find per-department top earners?

Group by department first, then sort and limit within each group.

JAVA
package com.bank.hr;

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

public class TopEarnerPerDept {
    public static void main(String[] args) {
        List<Employee> employees = Employee.getSampleEmployees();

        // Top earner per department
        Map<String, Employee> topPerDept = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.collectingAndThen(
                    Collectors.maxBy(
                        Comparator.comparing(Employee::getSalary)),
                    Optional::orElseThrow)));

        topPerDept.forEach((dept, emp) ->
            System.out.println(dept + ": " + emp.getName()
                + " Rs." + emp.getSalary()));

        // Top 2 earners per department
        Map<String, List<Employee>> top2PerDept = employees.stream()
            .collect(Collectors.groupingBy(Employee::getDepartment))
            .entrySet().stream()
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                e -> e.getValue().stream()
                    .sorted(Comparator.comparing(Employee::getSalary).reversed())
                    .limit(2)
                    .toList()));

        top2PerDept.forEach((dept, emps) -> {
            System.out.println(dept + ":");
            emps.forEach(e ->
                System.out.println("  " + e.getName() + " Rs." + e.getSalary()));
        });
    }
}
TEXT
TRADING: Rajesh Rs.1800000
RISK: Deepak Rs.1500000
OPERATIONS: Suresh Rs.1100000
COMPLIANCE: Vikram Rs.1300000

What are common interview follow-up questions?

  • "How do you find the employee with the Nth-highest salary?" First find the Nth salary amount, then filter employees matching that amount.
  • "What if N is larger than the number of distinct salaries?" findFirst() returns Optional.empty(). Handle it with orElse or orElseThrow.
  • "How do you find bottom 3 earners?" Use sorted(comparing(salary)) without .reversed(), then .limit(3).
  • "How do you handle null salaries?" Filter them out before sorting: filter(e -> e.getSalary() != null).

Visual Overview

DIAGRAM
  Nth-highest salary pattern:

  Stream<Employee>
       │
       │  .map(Employee::getSalary)
       ▼
  Stream<BigDecimal>  [1800000, 1200000, 1500000, 1800000, ...]
       │
       │  .distinct()
       ▼
  Stream<BigDecimal>  [1800000, 1200000, 1500000, ...] (unique)
       │
       │  .sorted(reverseOrder())
       ▼
  Stream<BigDecimal>  [1800000, 1500000, 1400000, 1200000, ...]
       │
       │  .skip(n - 1)  (e.g., skip(2) for 3rd)
       ▼
  Stream<BigDecimal>  [1400000, 1200000, ...]
       │
       │  .findFirst()
       ▼
  Optional<BigDecimal>  [1400000]   ← 3rd-highest

Key Takeaway

Finding the highest salary uses map(Employee::getSalary).max(naturalOrder()), which returns Optional. For the second-highest, the critical step is distinct(): it removes duplicate salary amounts so that two employees earning the same top salary do not occupy two positions. The pattern is map(getSalary).distinct().sorted(reverseOrder()).skip(1).findFirst().

The Nth-highest generalizes to skip(n - 1).findFirst(). If N exceeds the number of distinct salaries, findFirst() returns empty. For top N earners as Employee objects, use sorted(comparing(salary).reversed()).limit(n) without distinct, because two employees earning the same salary are two separate people.

Per-department top earners combine groupingBy with maxBy(comparing(salary)) for the single top earner, or group then sort-and-limit within each group for the top N. The two-step approach (group, then post-process each group with a second stream) is the standard pattern.

Remember one sentence: distinct is the key to Nth-highest salary because it collapses duplicate amounts into one position, and the pattern is map-distinct-sorted-skip-findFirst.

---

Share
Use ← → arrow keys to navigate