skip to content

What is the difference between forEach() and forEachOrdered() on a stream, especially when the stream is parallel?

level: middleimportance: must knowfreq 52%

answer

  1. forEach = no order guarantee (scrambled in parallel)
  2. forEachOrdered = encounter order, even in parallel
  3. Ordering costs the parallel speedup
  4. Both are side-effect terminals returning void
  5. Neither makes your Consumer thread-safe

basics

~20 s

forEach() runs an action on each element with no order guarantee — in a parallel stream elements may be handled in any order. forEachOrdered() always processes elements in encounter order, even in parallel, at the cost of speed.

solid answer

~50 s

Both are terminal operations that apply a consumer to each element for its side effect. forEach() makes no ordering promise: on a sequential stream it typically follows encounter order, but on a parallel stream the action runs concurrently and elements arrive in an arbitrary, run-to-run-varying order. forEachOrdered() guarantees the action is invoked in the stream's encounter order regardless of parallelism, which forces the framework to serialize the callbacks in order and therefore sacrifices the parallel speedup of the side-effect phase. Choose forEach() when the action is order-independent (e.g. incrementing an atomic counter, sending to a thread-safe sink) and you want maximum throughput. Choose forEachOrdered() when the side effect must happen in order, such as writing lines to a file or printing in sequence. Note: even forEach() does not make the consumer thread-safe — you are still responsible for safe shared state.

code

java · 8 lines
java
List<Integer> nums = List.of(1, 2, 3, 4, 5);

// Parallel + forEach: output order is unpredictable
nums.parallelStream().forEach(System.out::println);

// Parallel + forEachOrdered: prints 1 2 3 4 5 in order,
// but the ordering constraint removes the parallel speedup of the print step
nums.parallelStream().forEachOrdered(System.out::println);

go deeper

for a junior

Knows forEach() runs an action per element and that forEachOrdered() keeps them in order.

for a middle

Explains that forEach() gives no order guarantee in parallel while forEachOrdered() preserves encounter order, and that the guarantee costs the parallel speedup.

for a senior

Recognizes the thread-safety implications of parallel forEach() and steers side-effecting accumulation toward collect/reduce; knows forEachOrdered() is a no-op constraint on unordered streams.

for a principal

Treats parallel side effects as a correctness/observability risk, sets conventions favoring collect over forEach for results, and reasons about whether the ordering cost makes parallelism worthwhile at all.

## What these operations do `forEach(Consumer)` and `forEachOrdered(Consumer)` are **terminal operations** that exist for **side effects**: you pass an action (a `Consumer`) that is applied to each element. Unlike `map`/`filter`, they return nothing (`void`) — the point is *doing something* (printing, writing, accumulating into an external sink), not producing a new stream. ## forEach(): no ordering guarantee `forEach()` explicitly **does not guarantee** the order in which the action runs. - On a **sequential** stream it *happens* to follow encounter order in practice, but the spec does not promise it. - On a **parallel** stream the action is invoked **concurrently on multiple threads**, so elements are processed in a non-deterministic order that can vary between runs. This is the whole point: by not promising order, it lets the framework run the side-effect phase fully in parallel. ## forEachOrdered(): encounter order preserved `forEachOrdered()` **guarantees** the action is invoked in the stream's **encounter order**, *even on a parallel stream*. To honor that, the framework must effectively serialize the callbacks back into order, which **gives up the parallel speedup** of the traversal's side-effect step (the upstream pipeline can still run in parallel, but the consumer calls are ordered). If the stream has no encounter order (unordered source), `forEachOrdered()` has no order to preserve and behaves like `forEach()`. ## Thread-safety caveat Neither method makes your `Consumer` thread-safe. With a parallel `forEach()`, your action may run on many threads at once — so mutating a plain `ArrayList` or a non-atomic counter from inside it is a **data race**. Use thread-safe sinks (e.g. an `AtomicLong`, a concurrent collection) or, better, prefer a `collect`/`reduce` that the framework parallelizes safely. ## How to choose - **forEach()** — when the side effect is **order-independent** and you want throughput: e.g. `set.parallelStream().forEach(item -> metrics.record(item))` where order does not matter. - **forEachOrdered()** — when the side effect **must occur in encounter order**: e.g. writing log lines to a file or `System.out.print` so output reads in sequence. ## A subtle trap People sometimes write `parallelStream().forEach(System.out::println)` and are surprised the output is scrambled. That is forEach() doing exactly what it promises. If ordered output is required, use `forEachOrdered()` — but realize you have just thrown away most of the parallelism, so a sequential stream may be simpler and just as fast.

  • Does forEachOrdered() on an unordered (HashSet-sourced) stream still impose an order?
    No. With no encounter order to preserve, forEachOrdered() has nothing to enforce and effectively behaves like forEach().
  • If you must collect results in order from a parallel stream, is forEachOrdered into a list the right tool?
    No — prefer collect(Collectors.toList()), which is parallel-safe and preserves encounter order. forEach/forEachOrdered into a shared mutable list risks a data race or sacrifices parallelism.

saying these in an interview costs you the question

  • Believing forEach() preserves order in parallel — it does not.
  • Thinking forEach() is automatically thread-safe for shared mutable state.
  • Assuming forEachOrdered() keeps full parallel speed — it serializes the ordered callbacks.
  • Using forEach into a non-concurrent collection to build a result (race) instead of collect().

context