Interview Prep Zoneby CuriouserLabs

Question 2 of 5 · staff level

When should you use parallel streams — and when are they a production incident waiting to happen?

🎤 Say this first

Parallel streams split the source with a Spliterator and run on the shared ForkJoinPool.commonPool. They pay off only when work is CPU-bound, data is large, per-element cost is non-trivial, and the source splits well (arrays yes, iterators/LinkedList no). They're dangerous when the lambda blocks on I/O (starves the common pool JVM-wide), when operations depend on encounter order, or when used inside a servlet container where you don't control what else shares the pool.

The full picture

  • The NQ heuristic: N (elements) × Q (cost per element) should be large (~10,000+ 'units') before parallelism pays for its splitting/merging overhead.
  • Source splittability: ArrayList, arrays, IntStream.range split perfectly; Stream.iterate and BufferedReader.lines split terribly.
  • Shared pool hazard: every parallel stream in the JVM shares commonPool (size = cores − 1). One team's blocking lambda throttles everyone. Workaround (submitting from a custom FJ pool) is undocumented behavior — treat as a smell.
  • Merge costs: collect(toMap) and ordered limit/findFirst pay heavy merge/ordering taxes; groupingByConcurrent + unordered sources ease this.
  • Accumulator safety: never mutate shared state in forEach — use collect (mutable reduction) which gives each thread its own container.
The classic anti-pattern
// DON'T: blocking I/O on the common pool
orders.parallelStream()
      .map(o -> httpClient.enrich(o))   // blocks a commonPool worker!
      .toList();

// DO: bounded executor (or virtual threads) for I/O-bound fan-out
try (var scope = StructuredTaskScope.open()) {
    var tasks = orders.stream()
        .map(o -> scope.fork(() -> httpClient.enrich(o)))
        .toList();
    scope.join();
}

🔄 Likely follow-up questions

  • How would you prove a parallel stream helped? (JMH + realistic data sizes)
  • Why is findFirst slower than findAny in parallel?
  • What changed for parallel streams with virtual threads — do they compose?