Definition
- forEach in parallel invokes the action as each thread finishes, so output order is non-deterministic.
- forEachOrdered coordinates threads to deliver results in the stream's encounter order.
- Computation is still parallel: only the ordered emission step is constrained.
- Trade-off: forEachOrdered reduces parallel benefit because threads must wait to deliver in sequence.
- Alternative: collect into a
List(preserves encounter order) and iterate sequentially.
At the National Bank of India, many clerks process transaction slips in parallel. With plain forEach, each clerk shouts out their result as soon as they finish, so the output is jumbled. With forEachOrdered, they queue up and announce results in the original slip order, even though the processing was parallel.
Understanding the Concepts
What is the problem with forEach in parallel?
On a parallel stream, forEach invokes the action as soon as any thread finishes processing an element. There is no coordination between threads about output order. This means the printed or processed order is non-deterministic and changes on every run.
How do you demonstrate the difference?
package com.bank.hr;
import java.math.BigDecimal;
import java.util.List;
public class ForEachOrderDemo {
record Employee(String id, String name, BigDecimal salary) {}
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("SAV2001", "Rajesh", new BigDecimal("1800000")),
new Employee("SAV2002", "Priya", new BigDecimal("1200000")),
new Employee("SAV2003", "Meera", new BigDecimal("1500000")),
new Employee("SAV2004", "Deepak", new BigDecimal("900000")),
new Employee("SAV2005", "Amit", new BigDecimal("1050000"))
);
System.out.println("--- forEach (unordered) ---");
staff.parallelStream()
.forEach(e -> System.out.println(e.name()));
System.out.println("\n--- forEachOrdered (ordered) ---");
staff.parallelStream()
.forEachOrdered(e -> System.out.println(e.name()));
}
}
--- forEach (unordered) ---
Meera
Rajesh
Deepak
Amit
Priya
--- forEachOrdered (ordered) ---
Rajesh
Priya
Meera
Deepak
Amit
How does forEachOrdered work internally?
The computation still runs in parallel: threads process chunks independently. But the terminal step coordinates so that the action is invoked in the stream's encounter order. Thread-2 may finish before Thread-1, but it waits until Thread-1 has delivered its elements before delivering its own.
This partial serialization at the end is why forEachOrdered reduces the parallel benefit: the more ordered emission matters, the less parallelism you gain.
How is forEach different from forEachOrdered?
| Aspect | forEach | forEachOrdered |
|---|---|---|
| Order guarantee | None in parallel | Encounter order preserved |
| Parallel speed | Full benefit | Reduced (threads wait to emit in order) |
| Sequential behavior | Same as forEachOrdered | Same as forEach |
| Use when | Order does not matter | Order matters for output |
What are common mistakes?
Mistake: Assuming forEach preserves order in parallel because it does sequentially.
// WRONG assumption: forEach preserves order in parallel
staff.parallelStream()
.sorted(Comparator.comparing(Employee::salary))
.forEach(e -> System.out.println(e.name()));
// sorted establishes order, but forEach ignores it in parallel
// CORRECT: use forEachOrdered to respect the sorted order
staff.parallelStream()
.sorted(Comparator.comparing(Employee::salary))
.forEachOrdered(e -> System.out.println(e.name()));
What about collecting into a list as an alternative?
// Alternative: collect preserves encounter order, then iterate
List<Employee> ordered = staff.parallelStream()
.sorted(Comparator.comparing(Employee::salary))
.collect(java.util.stream.Collectors.toList());
ordered.forEach(e -> System.out.println(e.name()));
// List iteration is always in insertion order
This is often cleaner than forEachOrdered when you need the data for more than just printing.
What about unordered sources?
An unordered source (like a HashSet) has no encounter order to preserve. Calling forEachOrdered on an unordered stream still processes elements, but the order is not meaningful because the source itself has no defined order.
You can also call unordered() to drop the ordering constraint, which can improve parallel performance for operations like distinct and limit.
What should a senior engineer know?
The decision is a trade-off: if order does not matter, use forEach for full parallel speed. If order matters, choose between forEachOrdered (ordered emission) and collecting into a list (ordered storage for later use). A senior engineer also knows that encounter order depends on the source being ordered: an ArrayList has encounter order, a HashSet does not.
What are common interview follow-up questions?
- "When should you use forEach vs forEachOrdered?" Use
forEachwhen order does not matter;forEachOrderedwhen it does. - "Does forEachOrdered negate all parallel benefit?" No: computation is still parallel; only emission is ordered.
- "Can you use forEachOrdered with a sequential stream?" Yes, but it behaves identically to
forEachsince there is only one thread.
Visual Overview
parallelStream().forEach(print) ┌──────────────────────────────────┐ │ Thread-1 finishes Meera → print│ │ Thread-3 finishes Rajesh → print│ ← unordered output │ Thread-2 finishes Priya → print│ └──────────────────────────────────┘ parallelStream().forEachOrdered(print) ┌──────────────────────────────────┐ │ Thread-3 finishes Rajesh → wait │ │ Thread-1 finishes Meera → wait │ │ Emit: Rajesh (1st) → Priya (2nd)│ ← ordered output │ Emit: Meera (3rd) → ... │ └──────────────────────────────────┘
Key Takeaway
On a parallel stream, plain forEach gives no guarantee about the order in which the action runs. Each worker thread invokes the action as soon as it finishes an element, so the output comes out jumbled and differs run to run. forEachOrdered instead guarantees the action is applied in the stream's encounter order by coordinating the threads to deliver their results in sequence.
At the National Bank of India, a statement printer processes transactions in parallel for speed but must print them in chronological order. This is exactly where forEachOrdered applies: the heavy computation runs in parallel, and only the final ordered emission is constrained.
The trade-off is that forEachOrdered reduces the parallel benefit because threads must wait to emit in order. If order does not matter, use plain forEach for full speed. If order matters but you also need the data for further processing, consider collecting into a list (which preserves encounter order) and then iterating sequentially.
Remember one sentence: forEachOrdered preserves encounter order in parallel at the cost of some speed, while forEach trades ordering for full throughput.
---