Definition
- Step 1:
groupingBy(department, averagingDouble(salary))producesMap. - Step 2:
entrySet().stream().max(comparingByValue())returns the department with the highest average. - Same pattern as finding the department with the most employees, but averaging instead of counting.
- Precision: averagingDouble has floating-point drift; for audited reports use BigDecimal arithmetic.
The National Bank of India compensation committee asks: 'Which department pays the most on average?' First compute the average salary per department, then scan the averages for the highest one.
Understanding the Concepts
What does this question ask?
This is the two-step aggregate-then-max pattern. Step 1 computes averages per department. Step 2 finds the department with the highest average.
How do you find the department with the highest average salary?
package com.bank.hr;
import java.util.*;
import java.util.stream.Collectors;
public class HighestAvgSalaryDept {
public static void main(String[] args) {
List<Employee> employees = Employee.getSampleEmployees();
Map<String, Double> avgByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(
e -> e.getSalary().doubleValue())));
Optional<Map.Entry<String, Double>> highest = avgByDept.entrySet()
.stream()
.max(Map.Entry.comparingByValue());
highest.ifPresent(e ->
System.out.printf("Highest avg salary: %s (Rs.%.2f)%n",
e.getKey(), e.getValue()));
}
}
Highest avg salary: TRADING (Rs.1500000.00)
How do you handle ties?
double maxAvg = avgByDept.values().stream()
.mapToDouble(Double::doubleValue).max().orElse(0);
List<String> tied = avgByDept.entrySet().stream()
.filter(e -> Double.compare(e.getValue(), maxAvg) == 0)
.map(Map.Entry::getKey)
.toList();
What are common mistakes?
Mistake: using averagingDouble directly on BigDecimal without conversion.
// WRONG: compile error
Double wrong = employees.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
// CORRECT: convert to double
Double correct = employees.stream()
.collect(Collectors.averagingDouble(
e -> e.getSalary().doubleValue()));
How do you rank all departments by average salary?
LinkedHashMap<String, Double> ranked = avgByDept.entrySet().stream()
.sorted(Map.Entry.<String, Double>comparingByValue().reversed())
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue,
(a, b) -> a, LinkedHashMap::new));
ranked.forEach((dept, avg) ->
System.out.printf("%s: Rs.%.2f%n", dept, avg));
What about using exact BigDecimal arithmetic?
Map<String, BigDecimal> exactAvg = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.collectingAndThen(
Collectors.toList(),
list -> {
BigDecimal sum = list.stream()
.map(Employee::getSalary)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return sum.divide(
BigDecimal.valueOf(list.size()),
2, java.math.RoundingMode.HALF_UP);
})));
For audited reports, double averaging introduces floating-point drift. Use BigDecimal with explicit rounding.
What about combining both steps with collectingAndThen?
import static java.util.stream.Collectors.*;
Optional<Map.Entry<String, Double>> result = employees.stream()
.collect(collectingAndThen(
groupingBy(Employee::getDepartment,
averagingDouble(e -> e.getSalary().doubleValue())),
map -> map.entrySet().stream()
.max(Map.Entry.comparingByValue())));
This wraps both steps into a single collect call, though the two-step approach is more readable.
What are common interview follow-up questions?
- "How do you find the department with the lowest average?" Replace max with min.
- "How do you exclude departments with fewer than 3 employees?" Filter the original stream or the result map.
- "How do you use exact BigDecimal arithmetic?" Group, then compute average per group with BigDecimal reduce and divide.
Visual Overview
Step 1: groupingBy(dept, averagingDouble(salary)) ┌────────────────────────────────────┐ │ TRADING → Rs.1,500,000.00 │ │ RISK → Rs.1,200,000.00 │ │ OPERATIONS → Rs.900,000.00 │ │ COMPLIANCE → Rs.1,050,000.00 │ └────────────────────────────────────┘ Step 2: max(comparingByValue()) → TRADING (Rs.1,500,000.00) ← highest
Key Takeaway
Finding the department with the highest average salary uses the same two-step pattern as finding the largest department. Step 1 groups by department with averagingDouble as the downstream. Step 2 streams the entrySet and finds max by value.
For tied departments, find the max average first, then filter all entries matching that value. For a complete ranking, sort the entrySet by value descending and collect to a LinkedHashMap.
Remember one sentence: groupingBy with averagingDouble produces per-department averages, then max on the entrySet finds the highest-paying department.
---