Definition
- partitioningBy(predicate) splits elements into exactly two groups:
trueandfalse. - 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.
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(", ")));
});
}
}
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 |
// 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:
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)));
Loans: Male=1, Female=1
IT: Male=1, Female=2
What are common mistakes?
Mistake: assuming groupingBy on boolean guarantees both keys.
// 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?
- "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
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.
---