Back to Stream: Grouping & Partitioning
Q14Question 14 of 22

Group employees by department, then partition each group into male/female

Key Concepts
  • groupingBy(dept, partitioningBy(isMale)) nests a partition inside each department group
  • partitioningBy always produces two keys: true and false, even if one side is empty
  • The result type is Map<String, Map<Boolean, List<Employee>>>
  • partitioningBy is more efficient than groupingBy on a boolean for a binary split
  • You can nest a further downstream inside partitioningBy, such as counting() for gender counts per department
  • Collector composition is how multi-level analytics are built in a single pass

Definition

groupingBy(dept, partitioningBy(isMale)) first buckets by department, then splits each bucket into true (male) and false (female).
  • partitioningBy(predicate) splits elements into exactly two groups: true and false.
  • Nested inside groupingBy, it creates a two-level structure: department, then gender.
  • Both keys always exist, even if one side is empty: no missing-key surprises.
  • You can nest a downstream collector inside partitioningBy for further aggregation.
  • The result type is Map>>.

Priya at the National Bank of India first sorts files into department trays, then inside each tray she splits them into two piles: one pile matches 'is male' and the other does not. Two levels of sorting.

Understanding the Concepts

How do you nest partitioning inside grouping?

partitioningBy used as the downstream of groupingBy creates a two-level map: the outer key is the department, the inner key is a boolean.

JAVA
package com.bank.hr;

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

public class DeptGenderPartition {
    public static void main(String[] args) {
        List<Employee> employees = List.of(
            new Employee("Rajesh", "Loans",  Gender.MALE),
            new Employee("Priya",  "Loans",  Gender.FEMALE),
            new Employee("Amit",   "IT",     Gender.MALE),
            new Employee("Kavita", "IT",     Gender.FEMALE),
            new Employee("Neha",   "IT",     Gender.FEMALE)
        );

        Map<String, Map<Boolean, List<Employee>>> result =
            employees.stream().collect(
                Collectors.groupingBy(
                    Employee::getDepartment,
                    Collectors.partitioningBy(
                        e -> e.getGender() == Gender.MALE)));

        result.forEach((dept, partition) -> {
            System.out.println(dept + ":");
            System.out.println("  Male:   " + partition.get(true).stream()
                .map(Employee::getName).collect(Collectors.joining(", ")));
            System.out.println("  Female: " + partition.get(false).stream()
                .map(Employee::getName).collect(Collectors.joining(", ")));
        });
    }
}
TEXT
Loans:
  Male:   Rajesh
  Female: Priya
IT:
  Male:   Amit
  Female: Kavita, Neha

Why choose partitioningBy over groupingBy on a boolean?

| Feature | partitioningBy | groupingBy on boolean |
|---|---|---|
| Both true/false keys present | Always | Only if both have elements |
| Efficiency | Slightly better (specialised) | General-purpose |
| Use case | Binary split | Any number of groups |

JAVA
// partitioningBy: both keys always exist
Map<Boolean, List<Employee>> partition =
    employees.stream().collect(
        Collectors.partitioningBy(e -> e.getGender() == Gender.MALE));
System.out.println(partition.get(true));   // always non-null
System.out.println(partition.get(false));  // always non-null, may be empty list

// groupingBy: might be missing a key
Map<Boolean, List<Employee>> grouped =
    employees.stream().collect(
        Collectors.groupingBy(e -> e.getGender() == Gender.MALE));
System.out.println(grouped.get(false)); // null if no females!

How do you get counts per gender per department?

Nest counting() inside partitioningBy:

JAVA
Map<String, Map<Boolean, Long>> genderCounts =
    employees.stream().collect(
        Collectors.groupingBy(
            Employee::getDepartment,
            Collectors.partitioningBy(
                e -> e.getGender() == Gender.MALE,
                Collectors.counting())));

genderCounts.forEach((dept, counts) ->
    System.out.println(dept + ": Male=" + counts.get(true)
        + ", Female=" + counts.get(false)));
TEXT
Loans: Male=1, Female=1
IT: Male=1, Female=2

What are common mistakes?

Mistake: assuming groupingBy on boolean guarantees both keys.

JAVA
// WRONG: groupingBy may not have false key if all employees are male
Map<Boolean, List<Employee>> grouped =
    maleOnlyTeam.stream().collect(
        Collectors.groupingBy(e -> e.getGender() == Gender.MALE));
List<Employee> females = grouped.get(false);  // null!

// CORRECT: use partitioningBy
Map<Boolean, List<Employee>> partitioned =
    maleOnlyTeam.stream().collect(
        Collectors.partitioningBy(e -> e.getGender() == Gender.MALE));
List<Employee> females2 = partitioned.get(false);  // empty list, never null

What should a senior engineer know?

  • Collectors compose: groupingBy, partitioningBy, and a terminal collector like counting or mapping can all nest to build multi-level analytics in one pass.
  • partitioningBy guarantees both keys, which simplifies downstream code that accesses both sides.
  • For more than two categories (male, female, other), use groupingBy on the enum instead of partitioningBy.
  • The cost is a single stream traversal regardless of nesting depth.

What are common interview follow-up questions?

DIAGRAM
- "How do you partition by salary above/below threshold?" partitioningBy(e -> e.getSalary() > 80000).
- "How do you add a third gender category?" Switch to groupingBy on the Gender enum.
- "How do you get average salary per gender per department?" Nest averagingDouble inside partitioningBy inside groupingBy.

Visual Overview

DIAGRAM
  Stream<Employee>
  [Rajesh:Loans:M, Priya:Loans:F, Amit:IT:M, Kavita:IT:F, Neha:IT:F]
       │
       │  groupingBy(dept)
       ▼
  Loans → [Rajesh:M, Priya:F]
  IT    → [Amit:M, Kavita:F, Neha:F]
       │
       │  partitioningBy(isMale)
       ▼
  Loans → {true: [Rajesh], false: [Priya]}
  IT    → {true: [Amit],   false: [Kavita, Neha]}

Key Takeaway

To split each department into male and female, nest partitioningBy inside groupingBy. The outer groupingBy buckets by department, and the inner partitioningBy splits by a gender predicate, producing a nested map with both true and false keys always present.

partitioningBy guarantees both keys even when one side is empty, which means a department with only men still reports an empty but present female list rather than a missing key. On a diversity report at the National Bank of India, that guarantee eliminated null checks in downstream code.

Collector composition is the key principle: groupingBy, partitioningBy, and a terminal collector like counting all nest cleanly to build multi-level analytics in a single stream traversal.

Remember one sentence: partitioningBy always produces both true and false keys, and nesting it inside groupingBy creates a two-level department-then-gender structure in one pass.

---

Share
Use ← → arrow keys to navigate