Tuesday, November 3, 2026

Java Stream API Trick: Finding Employees Earning Above Their Department's Average

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.

Java Stream API Trick: Finding Employees Earning Above Their Department's Average


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:

  1. Pre-calculate the averages into a lookup Map.
  2. 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 by department and instantly reduces the grouped lists into a single average Double value.
  • 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