groupingBy vs partitioningBy — Group-By with Downstream Collectors
Fast recall
Quick revision
Core answer
Map> keyed by a classifier function — like SQL GROUP BY. `partitioningBy` is a specialized form that always produces a Map> — elements that match a predicate go to true, the rest to false. Both accept a downstream collector so you can aggregate within each group: count (counting()), sum/avg (summingInt, averagingDouble), collect to set (toSet()), join strings (joining), or even nest another groupingBy. Use partitioningBy when you have exactly two buckets defined by a predicate — active vs inactive users, passing vs failing scores. Use groupingBy when you have N categories — grouping by department, status, year. partitioningBy is more efficient than groupingBy(p ? "yes" : "no") because both keys (true and false) are always present in the result map (even with empty lists), and the data structure is optimized for the boolean case. For multi-level grouping (department → role → count), nest groupingBy calls. For specific Map types or thread safety, use the 3-arg variant: groupingBy(classifier, mapFactory, downstream) or groupingByConcurrent for parallel streams.Whether the candidate can use Collectors beyond `toList`/`toMap`, understands downstream collectors, knows when to choose `partitioningBy` over `groupingBy`, and can compose multi-level groupings
Using `groupingBy` with a boolean classifier instead of `partitioningBy`. Forgetting that `partitioningBy` always returns both `true` and `false` keys (even empty), but `groupingBy` only includes keys that have at least one element
Show the 3-arg variants for choosing the Map type (`TreeMap` for sorted, `LinkedHashMap` for insertion order), discuss `groupingByConcurrent` for parallel streams, and know that `partitioningBy` returns a specialized 2-entry Map for performance
Interview-ready guide
Interview answer
groupingBy and partitioningBy are the Stream API's GROUP BY. They differ by whether the key is arbitrary or boolean.
`groupingBy`
- Takes a classifier function that produces a key per element
- Returns
Mapwith elements gathered by key> - Like SQL
GROUP BY - Example: group employees by department
`partitioningBy`
- Takes a predicate — a yes/no question
- Returns
Map> - Specialized for the binary case
- Example: split employees into high earners vs the rest
Why `partitioningBy` exists
- Self-documenting: signals "this is a binary split"
- Returns a specialized 2-entry map — faster than
HashMap - Both keys are always present, even if empty — no null check needed
- With
groupingBy(predicate), if no element matches, thetruekey is missing
Downstream collectors — the real power
Both take a downstream collector to aggregate within each group:
counting()— number per groupsummingInt,averagingDouble— numeric aggregatesmapping(fn, downstream)— transform before collecting (e.g., emails into a Set)joining(", ")— string concatenation per groupsummarizingInt— min/max/avg/sum/count in one passmaxBy,minBy— best per group
Multi-level grouping
Nest groupingBy calls like nested SQL GROUP BY:
groupingBy(dept, groupingBy(role, counting()))- Result:
Map> - Can also nest
partitioningByinsidegroupingBy
Choosing the Map type — 3-arg variant
groupingBy(classifier, TreeMap::new, downstream)— sorted by keygroupingBy(classifier, LinkedHashMap::new, downstream)— insertion ordergroupingByConcurrent— for parallel streams, returnsConcurrentHashMap, faster but loses per-group order
Common patterns
- Top per group:
groupingBy(dept, collectingAndThen(maxBy(...), Optional::get)) - Frequency map:
groupingBy(identity(), counting()) - Histogram: classifier returns bucket name (
<18,18-29,30-49,50+) - Top-N per group: not built-in; post-process with
entrySet().stream()thensorted().limit(N)
Watch out for
- `groupingBy` skips empty groups — if you need all enum values, seed the map first
- Default order isn't guaranteed — use
LinkedHashMaporTreeMapvia 3-arg - Inner lists are mutable — wrap with
Collectors.toUnmodifiableList()for immutability - Null classifier result — NPE; use a sentinel or filter nulls first
- Deep nesting — cardinality can explode; profile before production
Recommendation: Reach for `partitioningBy` any time the question is binary — active vs inactive, valid vs invalid, premium vs free — because it's faster, more self-documenting, and guarantees both buckets exist. Use `groupingBy` for everything with N categories: department, status, year, region. Always pair groupingBy with a downstream collector like counting(), summingInt, or mapping(..., toSet()) rather than collecting to List and post-processing — it's faster and reads better. For sorted output, use the 3-arg variant with `TreeMap::new`. For parallel streams over large datasets, use `groupingByConcurrent` — it skips the per-thread merge overhead. Avoid going beyond two levels of nesting unless your domain genuinely requires it; flatter structures with composite keys (records or tuples) are easier to consume.
`groupingBy` vs `partitioningBy` — Group-By with Downstream Collectors
Direct Answer
groupingBy groups stream elements into a Map<K, List<T>> keyed by a classifier function — like SQL GROUP BY. partitioningBy is a specialized form that always produces a Map<Boolean, List<T>> — elements that match a predicate go to true, the rest to false. Both accept a downstream collector so you can aggregate within each group: count (counting()), sum/avg (summingInt, averagingDouble), collect to set (toSet()), join strings (joining), or even nest another groupingBy. Use partitioningBy when you have exactly two buckets defined by a predicate — active vs inactive users, passing vs failing scores. Use groupingBy when you have N categories — grouping by department, status, year. partitioningBy is more efficient than groupingBy(p ? "yes" : "no") because both keys (true and false) are always present in the result map (even with empty lists), and the data structure is optimized for the boolean case. For multi-level grouping (department → role → count), nest groupingBy calls. For specific Map types or thread safety, use the 3-arg variant: groupingBy(classifier, mapFactory, downstream) or groupingByConcurrent for parallel streams.
Common Mistakes & Pitfalls
Using groupingBy with a boolean classifier instead of partitioningBy. Forgetting that partitioningBy always returns both true and false keys (even empty), but groupingBy only includes keys that have at least one element
Check your understanding
Answer these without looking back at the article.
- Why does `partitioningBy` always include both `true` and `false` keys but `groupingBy` doesn't?
- How do you get the top-N elements per group with Collectors?
- What's the difference between `groupingBy` and `groupingByConcurrent`?
- How would you build a frequency map of words in a text?
- What happens if your classifier function returns `null`?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.