Definition
- 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?
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()));
});
}
}
Deepak Rs.1500000
Alternative: find max first, then max of the rest
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.
// 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?
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?
// 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
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.
---