What is Collectors.partitioningBy and how does it differ from groupingBy?
answer
- Predicate -> Map<Boolean, List<T>>
- Both true AND false keys always present
- Two-bucket special case of groupingBy
- Empty side = empty list, not missing key
- Accepts a downstream collector too
basics
~20 spartitioningBy splits elements into exactly two groups using a true/false test. The result is Map<Boolean, List<T>> with keys true and false. groupingBy can make any number of groups from any key; partitioningBy is the special two-bucket case.
solid answer
~40 sCollectors.partitioningBy(predicate) splits a stream into exactly two groups based on a boolean predicate, returning Map<Boolean, List<T>> with both keys (true and false) always present — even when one side is empty. That guaranteed presence of both keys is a key difference from groupingBy, where a key only appears if some element produced it. partitioningBy is essentially groupingBy specialized to a boolean classifier, but it is more efficient (it can use a two-slot structure instead of a hash map) and contractually always returns both buckets. Like groupingBy, it accepts a downstream collector: partitioningBy(predicate, counting()) gives Map<Boolean, Long>. Use it when you genuinely have a binary split — passing vs failing, even vs odd, valid vs invalid — and you want both sides reliably available.
code
java · 17 linesimport java.util.*;
import java.util.stream.*;
List<Integer> nums = List.of(1, 2, 3, 4, 5, 6);
Map<Boolean, List<Integer>> evenOdd = nums.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// {false=[1, 3, 5], true=[2, 4, 6]}
Map<Boolean, Long> counts = nums.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0, Collectors.counting()));
// {false=3, true=3}
// Even when nothing matches, both keys still exist:
Map<Boolean, List<Integer>> none = List.of(1, 3, 5).stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// {false=[1, 3, 5], true=[]} -- 'true' key present with empty listgo deeper
Knows partitioningBy splits into true/false buckets and returns Map<Boolean, List<T>>.
Articulates the key contractual difference: both boolean keys always present (empty list, not missing), and knows it accepts a downstream collector.
Chooses partitioningBy vs groupingBy deliberately on binary-ness and the always-both-keys guarantee, and knows the optimized two-slot backing.
Treats it as a domain-modeling choice (binary classification semantics, reporting empty buckets as zero) and reasons about its specialized collector implementation versus the general hash-based one.
## The idea Sometimes a group key is not an arbitrary value but a yes/no question: did the order ship? is the number even? did the test pass? For these binary splits the JDK offers a dedicated collector, `partitioningBy`. ## Terms - **Predicate**: a function `Predicate<T>` that returns a `boolean` for each element — the test that decides which side it lands on. - **Partition**: dividing a set into non-overlapping subsets that together cover everything. Here there are exactly two subsets: the elements where the predicate is `true`, and those where it is `false`. ## Signature and result ```java partitioningBy(Predicate<T> p) -> Map<Boolean, List<T>> ``` The map has **exactly two keys**: `Boolean.TRUE` and `Boolean.FALSE`. Crucially, *both keys are always present*, even if a partition is empty (you get an empty list, never a missing key). This is the main behavioral contract. ```java Map<Boolean, List<Integer>> evenOdd = nums.stream().collect(Collectors.partitioningBy(n -> n % 2 == 0)); List<Integer> evens = evenOdd.get(true); List<Integer> odds = evenOdd.get(false); ``` ## How it differs from groupingBy | | groupingBy | partitioningBy | |---|---|---| | Key source | any classifier `T -> K` | a boolean predicate `T -> boolean` | | Number of groups | 0..N (only keys that occur) | always exactly 2 | | Missing keys | absent if no element matched | both `true` and `false` always present | | Backing structure | a `HashMap` (general) | an optimized two-slot map | You *could* write `groupingBy(n -> n % 2 == 0)` and get a `Map<Boolean, List<Integer>>` too — but it would omit a key when one side is empty, and it uses a general hash map. `partitioningBy` guarantees both keys and is implemented more efficiently for the two-way case. ## With a downstream collector Just like `groupingBy`, the second argument is a downstream collector applied to each partition: ```java Map<Boolean, Long> passFailCounts = students.stream().collect( Collectors.partitioningBy(s -> s.score() >= 50, Collectors.counting())); // {false=<#failed>, true=<#passed>} ``` ## When to choose which - Use **partitioningBy** when the split is genuinely binary and you want both sides reliably available (e.g. to report 0 in an empty bucket). - Use **groupingBy** when the number of categories is open-ended or more than two. ## Gotcha Because keys are primitive-wrapper `Boolean`, calling `.get(true)` autoboxes fine, but remember the result map is small and fixed; do not iterate expecting arbitrary keys.
- If no element matches the predicate, what does partitioningBy.get(true) return?An empty list, not null. Both true and false keys are always present in the result, which is the main contractual difference from groupingBy.
- Could you replace partitioningBy with groupingBy on a boolean classifier? What changes?Yes, but groupingBy would omit a key when one side is empty and uses a general HashMap; partitioningBy guarantees both keys and uses an optimized two-slot structure.
saying these in an interview costs you the question
- Thinking partitioningBy can produce more than two groups
- Expecting a missing key when one partition is empty (it is an empty list)
- Assuming get(true) can return null
- Using partitioningBy for multi-category splits where groupingBy fits better