Java Stream API Trick: Finding Employees Earning Above Their Department's Average
Have you ever faced this classic interview or data-processing problem? You have a list of employees, and you need to extract only those who earn more than the average salary of their specific department.
While a SQL window function makes this easy (AVG(salary) OVER(PARTITION BY dept)), doing this efficiently in memory with Java Streams requires a strategic two-step approach to avoid performance traps.
Here is how to solve it cleanly in Java 8+.
❌ The Anti-Pattern to Avoid
A common mistake is trying to calculate the average inside a .filter() block on every iteration. This turns an efficient operation into an O(N²) performance nightmare because Java recalculates the average for the entire department over and over again for every single employee.
⚡ The Optimized O(N) Solution
The best approach is to split the logic into two steps:
- Pre-calculate the averages into a lookup Map.
- Filter the list using O(1) map lookups.
Here is the full implementation:
import java.util.*;
import java.util.stream.Collectors;
public class HighEarnerFinder {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("Alice", "Tech", 95000),
new Employee("Bob", "Tech", 50000),
new Employee("Charlie", "Tech", 45000),
new Employee("David", "HR", 70000),
new Employee("Eve", "HR", 40000)
);
// Step 1: Pre-calculate the average salary per department
Map<String, Double> avgSalaryPerDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(Employee::getSalary)
));
// Step 2: Filter employees exceeding their department's average
List<Employee> highEarners = employees.stream()
.filter(emp -> emp.getSalary() > avgSalaryPerDept.get(emp.getDepartment()))
.collect(Collectors.toList());
// Output results
highEarners.forEach(System.out::println);
}
}
class Employee {
private final String name;
private final String department;
private final double salary;
public Employee(String name, String department, double salary) {
this.name = name;
this.department = department;
this.salary = salary;
}
public String getName() { return name; }
public String getDepartment() { return department; }
public double getSalary() { return salary; }
@Override
public String toString() {
return name + " [" + department + "] - $" + salary;
}
}
🔍 How it Works Under the Hood
groupingBy+averagingDouble: This acts as a downstream collector. It groups your dataset bydepartmentand instantly reduces the grouped lists into a single averageDoublevalue.- O(1) Map Lookup: In the second stream,
.filter()grabs the department name from the current employee and checks it against our pre-calculated map. Because map lookups are virtually instantaneous, the filter step runs in linear time.

No comments:
Post a Comment