Back to Stream: Grouping & Partitioning
Q3Question 3 of 22

Group employees by department, then get the average salary per department into Map<String, Double>

Key Concepts
  • groupingBy(dept, averagingDouble(getSalary)) computes the mean salary per department
  • averagingDouble returns 0.0 for an empty group, though groupingBy only creates groups with elements
  • averagingDouble uses compensated summation internally for better floating-point accuracy
  • For an exact monetary average, sum as BigDecimal and divide with explicit RoundingMode
  • The result value type is Double (boxed), suitable for dashboard display
  • The pattern extends to summingDouble, summarizingInt, and other numeric downstreams

Definition

`groupingBy(dept, averagingDouble(getSalary))` gives the mean salary per department.
  • averagingDouble(mapper) computes the arithmetic mean of the double values extracted by the mapper.
  • Compensated summation: internally uses a compensated algorithm to reduce floating-point error.
  • Returns Double: the boxed average, suitable for display but not for exact monetary calculations.
  • Exact money average: sum as BigDecimal and divide with RoundingMode for audited figures.
  • Empty group: returns 0.0, though groupingBy only creates groups when elements exist.

Priya wants to know the average pay in each department. For every department she adds up the salaries and divides by how many people there are. The averaging downstream does both automatically.

Understanding the Concepts

How do you compute average salary per department?

JAVA
package com.bank.hr;

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

public class AvgSalaryByDept {
    public static void main(String[] args) {
        List<Employee> staff = List.of(
            new Employee("Rajesh",  "Loans",   new BigDecimal("95000")),
            new Employee("Priya",   "IT",      new BigDecimal("120000")),
            new Employee("Amit",    "Finance", new BigDecimal("85000")),
            new Employee("Kavita",  "Loans",   new BigDecimal("78000")),
            new Employee("Suresh",  "IT",      new BigDecimal("110000")));

        Map<String, Double> avgByDept = staff.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.averagingDouble(
                    e -> e.getSalary().doubleValue())));

        avgByDept.forEach((dept, avg) ->
            System.out.printf("%s: Rs. %.2f%n", dept, avg));
    }
}
TEXT
Loans: Rs. 86500.00
IT: Rs. 115000.00
Finance: Rs. 85000.00

When is Double acceptable vs when do you need BigDecimal?

| Use case | Precision needed | Approach |
|---|---|---|
| Dashboard display | Approximate | averagingDouble (Double) |
| Audited financial report | Exact | BigDecimal sum / count with RoundingMode |

How do you compute an exact BigDecimal average?

JAVA
import java.math.RoundingMode;

Map<String, BigDecimal> exactAvg = staff.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, RoundingMode.HALF_UP);
            })));

exactAvg.forEach((dept, avg) ->
    System.out.printf("%s: Rs. %s%n", dept, avg.toPlainString()));

What other numeric downstreams are available?

| Downstream | Result | Use case |
|---|---|---|
| counting() | Long | Head count |
| summingDouble(mapper) | Double | Total per group |
| averagingDouble(mapper) | Double | Mean per group |
| summarizingInt(mapper) | IntSummaryStatistics | Min, max, avg, sum, count |
| maxBy(comparator) | Optional | Largest element per group |
| minBy(comparator) | Optional | Smallest element per group |

What are common mistakes?

Mistake: assuming the double average is exact for money.

JAVA
// The double average may carry tiny representation error
// For exact monetary calculations, use BigDecimal division

What about sorted department keys?

JAVA
Map<String, Double> sorted = staff.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        TreeMap::new,
        Collectors.averagingDouble(e -> e.getSalary().doubleValue())));

What about summarizingInt for richer statistics?

JAVA
Map<String, IntSummaryStatistics> stats = staff.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        Collectors.summarizingInt(Employee::getAge)));

stats.forEach((dept, s) -> System.out.printf(
    "%s: count=%d, avg=%.1f, min=%d, max=%d%n",
    dept, s.getCount(), s.getAverage(), s.getMin(), s.getMax()));

What should a senior engineer know?

  • averagingDouble uses compensated summation internally for better accuracy.
  • For audited monetary figures, always use BigDecimal division with RoundingMode.
  • groupingBy with a downstream is a one-pass operation.
  • The pattern extends to any numeric downstream.

What are common interview follow-up questions?

  • "Is the double average exact for money?" No, use BigDecimal for exact figures.
  • "How do you get both sum and count?" Use summarizingDouble or summarizingInt.
  • "What about departments with one employee?" The average is just that employee's salary.

Visual Overview

DIAGRAM
  groupingBy(dept, averagingDouble(salary))

  Loans:   (95000 + 78000) / 2 = 86500.0
  IT:      (120000 + 110000) / 2 = 115000.0
  Finance: (85000) / 1 = 85000.0

  Map<String, Double>
  ┌──────────────────────────┐
  │  Loans   → 86500.0       │
  │  IT      → 115000.0      │
  │  Finance → 85000.0       │
  └──────────────────────────┘

Key Takeaway

To compute average salary per department I use groupingBy with averagingDouble as the downstream. It keeps a running sum and count per group and divides at the end, using compensated summation for better accuracy.

For dashboard display the double average is fine, but for audited financial reports I always sum as BigDecimal and divide with an explicit RoundingMode, because double carries tiny representation error.

In my banking project at the National Bank of India this drove a compensation-benchmarking view comparing average pay across departments. The pattern extends to counting, summingDouble, summarizingInt, maxBy, and minBy.

Remember one sentence: groupingBy(dept, averagingDouble(salary)) computes the mean salary per department in one pass, but for exact money use BigDecimal division.

---

Share
Use ← → arrow keys to navigate