Back to Stream Practice: Employee Analysis
Q16Question 16 of 43

Find department with highest total salary, then its top earner

Key Concepts
  • Three-step derivation: sum per department, find the max department, find the top earner in that department
  • Step 1 uses groupingBy(dept, summingDouble(salary)) to compute total salary per department
  • Step 2 uses max(comparingByValue()) on the entrySet to identify the highest-spending department
  • Step 3 filters employees by that department and uses max(comparing(salary)) for the top earner
  • Each step feeds the next: the result of one becomes the input for the following step
  • Keep each step as a separate named variable for clarity rather than chaining into one expression

Definition

Sum salaries per department, find the department with the highest total, then find the employee with the highest salary in that department.
  • Step 1: groupingBy(dept, summingDouble(salary)) produces total salary per department.
  • Step 2: max(comparingByValue()) on the entrySet identifies the highest-spending department.
  • Step 3: filter(dept == maxDept).max(comparing(salary)) finds the top earner in that department.
  • Three-step derivation: each step's result feeds into the next.
  • Clarity over brevity: separate named variables are easier to read and debug than a single chained expression.

The CFO at National Bank of India asks: 'In the department that costs us the most in total salaries, who is the highest-paid person?' Rajesh first totals each department's payroll, finds the costliest department, then looks up who earns the most there.

Understanding the Concepts

What does this three-step derivation involve?

This problem chains multiple aggregations: the output of one feeds the input of the next. Step 1 computes a departmental total, Step 2 identifies the maximum among those totals, and Step 3 drills into the winning department to find its top earner. Each step is a distinct stream operation.

How do you implement it with streams?

JAVA
package com.bank.hr;

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

public class TopEarnerInMaxDept {

    record Employee(String id, String name, String department, BigDecimal salary) {}

    public static void main(String[] args) {
        List<Employee> staff = List.of(
            new Employee("SAV2001", "Rajesh",  "TRADING",    new BigDecimal("1800000")),
            new Employee("SAV2002", "Priya",   "TRADING",    new BigDecimal("1200000")),
            new Employee("SAV2003", "Meera",   "TRADING",    new BigDecimal("1500000")),
            new Employee("SAV2004", "Deepak",  "RISK",       new BigDecimal("1500000")),
            new Employee("SAV2005", "Amit",    "RISK",       new BigDecimal("900000")),
            new Employee("SAV2006", "Vikram",  "COMPLIANCE", new BigDecimal("1300000")),
            new Employee("SAV2007", "Kavita",  "OPERATIONS", new BigDecimal("700000"))
        );

        // Step 1: Total salary per department
        Map<String, Double> totalByDept = staff.stream()
            .collect(Collectors.groupingBy(
                Employee::department,
                Collectors.summingDouble(e -> e.salary().doubleValue())));

        // Step 2: Department with highest total
        String maxDept = totalByDept.entrySet().stream()
            .max(Map.Entry.comparingByValue())
            .map(Map.Entry::getKey)
            .orElseThrow(() -> new NoSuchElementException("No departments found"));

        // Step 3: Top earner in that department
        Employee topEarner = staff.stream()
            .filter(e -> e.department().equals(maxDept))
            .max(Comparator.comparing(Employee::salary))
            .orElseThrow();

        System.out.printf("Highest-spending dept: %s (Rs.%.0f total)%n",
            maxDept, totalByDept.get(maxDept));
        System.out.printf("Top earner: %s (Rs.%s)%n",
            topEarner.name(), topEarner.salary());
    }
}
TEXT
Highest-spending dept: TRADING (Rs.4500000 total)
Top earner: Rajesh (Rs.1800000)

Can you combine all three steps into one expression?

Yes, using collectingAndThen, but it hurts readability.

JAVA
Optional<Employee> oneLiner = staff.stream()
    .collect(Collectors.collectingAndThen(
        Collectors.groupingBy(Employee::department,
            Collectors.summingDouble(e -> e.salary().doubleValue())),
        map -> map.entrySet().stream()
            .max(Map.Entry.comparingByValue())
            .map(Map.Entry::getKey)))
    .flatMap(dept -> staff.stream()
        .filter(e -> e.department().equals(dept))
        .max(Comparator.comparing(Employee::salary)));

The three-step version with named variables is far clearer for interviews and production code.

How is highest total different from highest average?

| Metric | Collector | Result |
|---|---|---|
| Highest total | summingDouble(salary) | Department spending the most overall |
| Highest average | averagingDouble(salary) | Department paying the most per person |

A large department with moderate salaries may have the highest total, while a small department with a few highly paid experts has the highest average.

What are common mistakes?

Mistake: Confusing highest average with highest total.

JAVA
// WRONG if the question asks for highest total
Collectors.averagingDouble(e -> e.salary().doubleValue())  // computes average

// CORRECT for highest total
Collectors.summingDouble(e -> e.salary().doubleValue())    // computes sum

What about edge cases?

  • Empty list: orElseThrow() throws NoSuchElementException. Handle with a default or a meaningful error.
  • Tie in total: max returns one arbitrarily. To handle ties, filter for all departments whose total equals the max.
  • Single employee in the winning department: That employee is trivially the top earner.

What about finding the bottom earner in the cheapest department?

Replace max with min in both steps.

JAVA
String minDept = totalByDept.entrySet().stream()
    .min(Map.Entry.comparingByValue())
    .map(Map.Entry::getKey)
    .orElseThrow();

Employee bottomEarner = staff.stream()
    .filter(e -> e.department().equals(minDept))
    .min(Comparator.comparing(Employee::salary))
    .orElseThrow();

What are common interview follow-up questions?

  • "How do you find the bottom earner in the cheapest department?" Replace max with min in both steps.
  • "How do you return the top 3 earners in that department?" Change Step 3 to sorted(desc).limit(3).
  • "Can this be done in one pass?" Not cleanly, because Step 2 depends on Step 1's complete result.

Visual Overview

DIAGRAM
  Step 1: Total salary per department
  ┌──────────────────────────────────────┐
  │  TRADING    = 1.8M + 1.2M + 1.5M    │
  │            = Rs.4,500,000            │
  │  RISK       = 1.5M + 0.9M           │
  │            = Rs.2,400,000            │
  │  COMPLIANCE = Rs.1,300,000           │
  │  OPERATIONS = Rs.700,000             │
  └──────────────────────────────────────┘
       │
       │  Step 2: max by value
       ▼
  TRADING (Rs.4,500,000) ← winner
       │
       │  Step 3: max salary in TRADING
       ▼
  [Rajesh 1.8M, Priya 1.2M, Meera 1.5M]
  → max(salary) = Rajesh (Rs.1,800,000)

Key Takeaway

Finding the top earner in the highest-spending department is a three-step derivation: sum per department, find the max department, then find the max salary within that department. Each step's result feeds into the next.

At the National Bank of India, the CFO uses this pattern to identify the costliest department and its highest-paid employee. Rajesh keeps each step as a separate named variable for clarity rather than chaining everything into one expression, which makes the code easy to read, debug, and explain in an interview.

This pattern appears in complex interview questions where the answer requires chaining multiple aggregations. The key insight is that each aggregation must complete before the next can begin, which is why the three-step approach with named variables is both correct and clear.

Remember one sentence: sum by department, find the max department, then find the max salary within that department, keeping each step as a separate readable variable.

---

Share
Use ← → arrow keys to navigate