Find Second Highest Salary Using Streams in Java
Problem
The second-highest salary is the largest salary level below the top one, so duplicate top salaries must count as a single level.
Given a list of employees, find the second-highest distinct salary and list everyone who earns it.
Java Program
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
public class SecondHighestSalary {
static class Employee {
private final String name;
private final int salary;
Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
String getName() { return name; }
int getSalary() { return salary; }
}
public static void main(String[] args) {
List<Employee> staff = Arrays.asList(
new Employee("Asha", 85000),
new Employee("Ravi", 92000),
new Employee("Meera", 92000),
new Employee("John", 78000),
new Employee("Liu", 85000));
Optional<Integer> second = staff.stream()
.map(Employee::getSalary)
.distinct() // 92000 appears twice but is one salary level
.sorted(Comparator.reverseOrder()) // highest first
.skip(1) // drop the highest
.findFirst();
System.out.println("Second highest salary: " + second.orElse(0));
staff.stream()
.filter(e -> e.getSalary() == second.orElse(-1))
.map(Employee::getName)
.forEach(name -> System.out.println("Earned by: " + name));
}
}Output
Core Logic
Reducing the salaries to distinct values, sorting them from highest to lowest and skipping the first one leaves the second-highest at the front.
- 1
map(Employee::getSalary)keeps only the salary numbers. - 2
distinct()collapses the two 92000 salaries into one level. - 3
sorted(Comparator.reverseOrder())orders them highest first, andskip(1)drops the top value. - 4
findFirst()returns anOptional, and a second stream lists the employees with that salary.
Key Point: Without distinct(), skipping the first value would return 92000 again — the most common mistake in this interview question.
Why: Sorting the distinct salaries dominates, and the distinct set may hold up to n values.
Key Concepts
Approach 2: Nth Highest Salary
import java.util.Arrays;
import java.util.List;
public class SecondHighestSalaryNth {
static int nthHighest(List<Integer> salaries, int n) {
return salaries.stream()
.distinct()
.sorted((a, b) -> b - a)
.skip(n - 1) // the nth highest sits after n - 1 larger values
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Fewer than " + n + " distinct salaries"));
}
public static void main(String[] args) {
List<Integer> salaries = Arrays.asList(85000, 92000, 92000, 78000, 85000);
for (int n = 1; n <= 3; n++) {
System.out.println(n + " -> " + nthHighest(salaries, n));
}
try {
nthHighest(salaries, 4);
} catch (IllegalArgumentException e) {
System.out.println("4 -> " + e.getMessage());
}
}
}
Output
Core Logic
Skipping n − 1 values instead of exactly one turns the same pipeline into a reusable nth-highest method.
- 1
skip(n - 1)discards the n − 1 higher salary levels. - 2
orElseThrowraises a clear error when there are fewer than n distinct salaries. - 3The loop prints the 1st, 2nd and 3rd highest values.
IllegalArgumentException.Key Point: Failing loudly is better than returning 0, which could be mistaken for a real salary.
Why: The same distinct-sort-skip pipeline runs for each n.
Key Concepts
Interview Questions
How would you write this in SQL?
A common answer is SELECT MAX(salary) FROM employees WHERE salary < (SELECT MAX(salary) FROM employees), or using DENSE_RANK() for any nth value.
Can you solve it without sorting?
Yes — keep two variables for the highest and second-highest distinct values in one pass, which is O(n).