Interview Prep Zoneby CuriouserLabs

Question 5 of 5 · senior level

Collectors deep-dive: groupingBy, toMap pitfalls, downstream collectors and writing your own.

🎤 Say this first

A Collector is four functions — supplier, accumulator, combiner, finisher — describing a (possibly parallel) mutable reduction. Power comes from composition: groupingBy(classifier, downstream) nests aggregations (counting, mapping, summing) in one pass. Classic pitfalls: toMap throws on duplicate keys unless you pass a merge function, and it NPEs on null values; groupingBy returns a HashMap with no ordering guarantees unless you supply the map factory.

The full picture

Composition over multiple passes
record Order(String region, String product, BigDecimal total) {}

// revenue per region, per product — ONE pass
Map<String, Map<String, BigDecimal>> revenue = orders.stream()
    .collect(groupingBy(Order::region,
             groupingBy(Order::product,
             reducing(BigDecimal.ZERO, Order::total, BigDecimal::add))));

// top order per region — no sorting the whole stream
Map<String, Optional<Order>> top = orders.stream()
    .collect(groupingBy(Order::region,
             maxBy(comparing(Order::total))));

// toMap: ALWAYS decide the merge policy
Map<String, Order> byId = orders.stream()
    .collect(toMap(Order::product, o -> o, (a, b) -> a.total().compareTo(b.total()) >= 0 ? a : b));
  • teeing(c1, c2, merger) (Java 12) runs two collectors in one pass — e.g., min and max together.
  • collectingAndThen(toList(), List::copyOf) finishes with an immutable result.
  • Custom collector: implement supplier/accumulator/combiner (+ characteristics CONCURRENT, UNORDERED, IDENTITY_FINISH); the combiner must be associative or parallel results silently corrupt.
  • mapMulti (Java 16) as a lower-allocation alternative to flatMap for 0..n expansions.

🔄 Likely follow-up questions

  • Why does the combiner need to be associative? What happens in parallel if it isn't?
  • How would you collect to a Guava/Immutable collection without a finisher copy?
  • groupingBy vs partitioningBy — when is the latter meaningfully better?