Back to Stream: Grouping & Partitioning
Q2Question 2 of 22

Group employees by department, then count employees in each department into Map<String, Long>

Key Concepts
  • groupingBy(dept, counting()) produces Map<String, Long> of head count per department
  • counting() is a downstream collector that tallies elements per group instead of collecting them into lists
  • This is more memory-efficient than grouping into lists and calling size() on each
  • For sorted keys, add TreeMap::new as the map factory in the three-argument form
  • The same pattern works for any classifier: city, age range, year, etc.
  • Alternative: toMap(getDepartment, e -> 1L, Long::sum) but groupingBy + counting is clearer

Definition

`groupingBy(dept, counting())` gives the head count per department as a Map<String, Long>.
  • Two-argument groupingBy(classifier, downstream) replaces the default list collector with a custom downstream.
  • counting() counts elements per group, producing Map.
  • Memory-efficient: never materialises per-group lists; only keeps running counts.
  • Sorted keys: groupingBy(classifier, TreeMap::new, counting()) for alphabetical order.
  • Alternative: toMap(getDept, e -> 1L, Long::sum) but less readable.

Instead of keeping a tray of cards per department, Priya only wants a tally: how many people are in each department. The counting downstream is that per-department tally.

Understanding the Concepts

How do you count employees per department?

JAVA
package com.bank.hr;

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

public class CountByDepartment {
    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, Long> counts = staff.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.counting()));

        counts.forEach((dept, count) ->
            System.out.println(dept + ": " + count));
    }
}
TEXT
Loans: 2
IT: 2
Finance: 1

Why is counting() more efficient than grouping then sizing?

| Approach | Memory | Steps |
|---|---|---|
| groupingBy(dept, counting()) | Only counts per group | One pass |
| groupingBy(dept) then map to size | Full lists per group | Two passes, more memory |

counting() never materialises the per-group lists: it only keeps a running counter per group.

How do you get sorted department keys?

JAVA
Map<String, Long> sorted = staff.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        TreeMap::new,
        Collectors.counting()));
// Keys: Finance, IT, Loans

What are common mistakes?

Mistake: grouping into lists and then calling size, wasting memory.

JAVA
// WRONG: builds full lists just to count them
Map<String, List<Employee>> groups = staff.stream()
    .collect(Collectors.groupingBy(Employee::getDepartment));
groups.forEach((dept, list) -> System.out.println(dept + ": " + list.size()));

// CORRECT: counting downstream avoids materialising lists
Map<String, Long> counts = staff.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment, Collectors.counting()));

What about filtering departments with count > 1?

JAVA
counts.entrySet().stream()
    .filter(e -> e.getValue() > 1)
    .forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));

What about the toMap alternative?

JAVA
// Alternative: toMap with merge function
Map<String, Long> alt = staff.stream()
    .collect(Collectors.toMap(
        Employee::getDepartment,
        e -> 1L,
        Long::sum));
// Works, but groupingBy + counting is more readable

What about counting with a different downstream?

The pattern extends to any downstream: summingDouble, averagingDouble, maxBy, mapping, etc.

JAVA
// Sum of salaries per department
Map<String, Double> salarySum = staff.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        Collectors.summingDouble(e -> e.getSalary().doubleValue())));

What should a senior engineer know?

  • counting() returns Long, not int, to avoid overflow for very large groups.
  • groupingBy with a downstream collector is a one-pass operation.
  • The same pattern works for any classifier: city, age range, year of joining.
  • For concurrent counting, use groupingByConcurrent(dept, counting()).

What are common interview follow-up questions?

DIAGRAM
- "How do you find the department with the most employees?" `max(comparingByValue())` on the entry set.
- "How do you sort by count descending?" Stream the entry set and sort by value reversed.
- "Can you do this without groupingBy?" Yes, with `toMap(dept, e -> 1L, Long::sum)`, but it is less clear.

Visual Overview

DIAGRAM
  List<Employee>
  [Rajesh/Loans, Priya/IT, Amit/Finance, Kavita/Loans, Suresh/IT]
       │
       │  collect(groupingBy(dept, counting()))
       │
       │  Loans:   +1 +1 = 2
       │  IT:      +1 +1 = 2
       │  Finance: +1     = 1
       ▼
  Map<String, Long>
  ┌──────────────────┐
  │  Loans   → 2     │
  │  IT      → 2     │
  │  Finance → 1     │
  └──────────────────┘

Key Takeaway

To count employees per department I use the two-argument groupingBy with Collectors.counting() as the downstream. Instead of collecting each group into a list, the counting downstream tallies how many employees fall into each group, producing a Map of department to Long count.

This never materialises per-group lists, so it uses far less memory than grouping into lists and then calling size. For sorted keys I use the three-argument form with TreeMap::new.

In my banking project at the National Bank of India this fed an HR capacity-planning dashboard showing head count per department. The same pattern extends to any downstream: summingDouble, averagingDouble, maxBy.

Remember one sentence: groupingBy(dept, counting()) counts employees per department in one pass without materialising per-group lists.

---

Share
Use ← → arrow keys to navigate