Back to Stream Practice: Employee Analysis
Q41Question 41 of 43

Find the employee with the second-highest salary (handle ties)

Key Concepts
  • First find the highest salary with max, then filter employees earning less and find the max again
  • Alternatively: map to distinct salaries, sort descending, skip(1), findFirst for the second-highest amount
  • Then filter all employees earning that second-highest amount to handle ties
  • distinct() is critical when multiple employees earn the highest salary
  • sorted(desc).skip(1).findFirst() skips one person, not one salary level, without distinct
  • Return List<Employee> for ties, not Optional<Employee>

Definition

Find the second-highest unique salary amount using distinct-sort-skip-findFirst, then filter all employees earning that amount to handle ties.
  • Approach 1: Find max salary, filter employees below it, find max of the remaining.
  • Approach 2: Map to distinct salary amounts, sort descending, skip(1), findFirst for the second-highest amount.
  • Handle ties: multiple employees may share the second-highest salary; return all of them.
  • distinct() is critical to avoid counting duplicate salary amounts as separate positions.

The bank's bonus committee says: 'The top earner already got a bonus. Now find who earns the second-most and give them a bonus too.' If Rajesh and Amit both earn the second-highest, both get the bonus.

Understanding the Concepts

What does this question ask?

Find the employee(s) with the second-highest salary, handling the case where multiple employees may share the same salary.

How do you find the second-highest salary and employees earning it?

JAVA
package com.bank.hr;

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

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

        // Step 1: find the second-highest unique salary
        Optional<BigDecimal> secondHighest = employees.stream()
            .map(Employee::getSalary)
            .distinct()
            .sorted(Comparator.reverseOrder())
            .skip(1)
            .findFirst();

        // Step 2: find ALL employees earning that amount
        secondHighest.ifPresent(salary -> {
            List<Employee> result = employees.stream()
                .filter(e -> e.getSalary().compareTo(salary) == 0)
                .toList();
            result.forEach(e ->
                System.out.println(e.getName() + " Rs." + e.getSalary()));
        });
    }
}
TEXT
Deepak Rs.1500000

Alternative: find max first, then max of the rest

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

maxSalary.ifPresent(max -> {
    Optional<BigDecimal> secondMax = employees.stream()
        .map(Employee::getSalary)
        .filter(s -> s.compareTo(max) < 0)
        .max(Comparator.naturalOrder());
    // secondMax is the second-highest salary
});

What are common mistakes?

Mistake: forgetting distinct and getting the same salary twice.

JAVA
// WRONG: without distinct, if two employees earn Rs.1800000,
// skip(1) gives Rs.1800000 again, not Rs.1500000
Optional<BigDecimal> wrong = employees.stream()
    .map(Employee::getSalary)
    .sorted(Comparator.reverseOrder())
    .skip(1)
    .findFirst();

// CORRECT: distinct collapses duplicate amounts
Optional<BigDecimal> correct = employees.stream()
    .map(Employee::getSalary)
    .distinct()
    .sorted(Comparator.reverseOrder())
    .skip(1)
    .findFirst();

What about finding second-highest per department?

JAVA
Map<String, Optional<BigDecimal>> secondPerDept = employees.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        Collectors.collectingAndThen(
            Collectors.mapping(Employee::getSalary,
                Collectors.toList()),
            salaries -> salaries.stream()
                .distinct()
                .sorted(Comparator.reverseOrder())
                .skip(1)
                .findFirst())));

What are the edge cases?

JAVA
// All employees earn the same: second-highest is empty
// Single employee: second-highest is empty
// Two employees same salary: distinct collapses to one, second is empty

What about returning all employees at the second-highest salary?

The two-step approach handles this: find the amount first, then filter all employees matching it. This correctly returns multiple employees when ties exist at the second-highest level.

What are common interview follow-up questions?

  • "What if all employees earn the same salary?" The second-highest does not exist; findFirst returns empty.
  • "How do you generalize to Nth-highest?" Change skip(1) to skip(n-1).
  • "How do you find the second-highest per department?" Group first, then apply the pattern within each group.

Visual Overview

DIAGRAM
  Salaries: [1800K, 1500K, 1400K, 1300K, 1200K, 1100K, 1000K, 900K, 800K, 700K]
       │
       │  .distinct() (remove duplicates)
       │  .sorted(reverseOrder())
       ▼
  [1800K, 1500K, 1400K, 1300K, ...]
       │
       │  .skip(1)  → second-highest amount
       ▼
  1500K
       │
       │  filter(salary == 1500K)
       ▼
  [Deepak]  ← employee(s) with second-highest salary

Key Takeaway

Finding the second-highest salary uses distinct-sort-skip-findFirst to get the salary amount, then filters all employees earning that amount to handle ties. The distinct step is critical: without it, duplicate top salaries count as separate positions.

The alternative approach finds the max salary first, then filters employees below it and finds the max again. Both work; the distinct-sort-skip approach generalizes more easily to Nth-highest.

Remember one sentence: distinct collapses duplicate salary amounts into one position, and the two-step approach (find the amount, then find the employees) handles ties correctly.

---

Share
Use ← → arrow keys to navigate