Back to Stream: reduce & Aggregate
Q2Question 2 of 9

forEach() vs forEachOrdered(): ordering guarantees in parallel streams

Key Concepts
  • Both are terminal operations that execute a Consumer on each element.
  • In sequential streams, both preserve encounter order and behave identically.
  • In parallel streams, forEach processes in whatever order threads complete (non-deterministic, faster).
  • In parallel streams, forEachOrdered preserves encounter order by making threads wait (deterministic, slower).
  • Prefer collect(toList()) over forEachOrdered for ordered results from parallel streams.
  • Use forEach for fire-and-forget side effects where order does not matter (logging, notifications).
  • forEachOrdered serialises the consumption step, partially negating the benefit of parallelism.

Definition

forEach has no order guarantee in parallel (fast); forEachOrdered preserves encounter order in parallel (slower); both are ordered in sequential streams.
  • forEach(Consumer): terminal, executes action on each element. No ordering guarantee in parallel.
  • forEachOrdered(Consumer): terminal, preserves encounter order even in parallel by serialising output.
  • Sequential: both behave identically (encounter order preserved).
  • Parallel forEach: elements processed in whichever order threads complete.
  • Parallel forEachOrdered: threads wait for earlier elements to be processed first.
  • Alternative: collect(toList()) preserves order with full parallelism benefit.

Eight clerks at National Bank of India process cheques simultaneously. forEach: each clerk shouts 'DONE!' as they finish, results in random order. forEachOrdered: clerks finish at different times, but results are announced in original cheque order. Clerk 3 waits for Clerks 1 and 2 to announce first. Same work done, but ordered announcement adds waiting time.

Understanding the Concepts

What is the difference between forEach and forEachOrdered?

Both are terminal operations that execute a Consumer on each element. The difference is ordering in parallel streams.

How do they behave differently?

JAVA
package com.bank.core;

import java.util.List;

public class ForEachDemo {
    public static void main(String[] args) {
        List<String> accounts = List.of("SAV2001", "SAV2002", "CUR3001", "SAV2003", "CUR3002");

        System.out.println("Sequential forEach:");
        accounts.stream().forEach(System.out::println); // ordered

        System.out.println("\nParallel forEach:");
        accounts.parallelStream().forEach(System.out::println); // scrambled!

        System.out.println("\nParallel forEachOrdered:");
        accounts.parallelStream().forEachOrdered(System.out::println); // ordered
    }
}
TEXT
Sequential forEach:
SAV2001
SAV2002
CUR3001
SAV2003
CUR3002

Parallel forEach:
CUR3001
SAV2001
CUR3002
SAV2003
SAV2002

Parallel forEachOrdered:
SAV2001
SAV2002
CUR3001
SAV2003
CUR3002

Comparison table

| Aspect | forEach | forEachOrdered |
|--------|---------|---------------|
| Sequential | Ordered | Ordered |
| Parallel | Non-deterministic (fast) | Ordered (slower) |
| Use case | Logging, notifications | Audit trails, ordered output |
| Performance | Full parallel benefit | Serialised consumption |

When should you use which?

| Scenario | Use |
|----------|-----|
| Order does not matter (logging) | forEach |
| Order matters in parallel | forEachOrdered or collect(toList()) |
| Need ordered collection | collect(toList()) (preferred) |

Prefer collect(toList()) over forEachOrdered for ordered parallel results. collect is designed for parallel accumulation with order preservation.

JAVA
// Preferred: collect preserves order AND parallelism
List<String> ordered = accounts.parallelStream()
    .filter(a -> a.startsWith("SAV"))
    .toList(); // ordered, parallel-friendly
ordered.forEach(System.out::println);

Why does forEachOrdered reduce parallel performance?

forEachOrdered only serialises the final output step. Intermediate operations (filter, map) still run in parallel. The slowdown is in the output ordering: threads must wait for earlier elements to be consumed first.

JAVA
// Processing is parallel, but output is sequential
accounts.parallelStream()
    .map(id -> {
        System.out.println(Thread.currentThread().getName() + " processes " + id);
        return id.toUpperCase();
    })
    .forEachOrdered(System.out::println); // output in encounter order

What are common mistakes?

JAVA
// WRONG: using forEach for ordered output from parallel stream
accounts.parallelStream()
    .forEach(System.out::println); // scrambled order!

// WRONG: using forEachOrdered when order doesn't matter
accounts.parallelStream()
    .forEachOrdered(a -> logger.info("Processing: {}", a)); // unnecessary serialisation

// CORRECT: use forEach when order doesn't matter
accounts.parallelStream()
    .forEach(a -> logger.info("Processing: {}", a)); // full parallel benefit

What about void return type?

Both forEach and forEachOrdered return void. They are terminal operations for side effects, not for producing results. For producing results, use collect or reduce.

What should a senior engineer know?

  • forEachOrdered only serialises the final output step. Intermediate operations still run in parallel.
  • For most use cases, collect(toList()) followed by sequential forEach is the cleanest pattern.
  • Use forEach for fire-and-forget side effects where order does not matter.
  • In banking audit trails where order matters, use forEachOrdered or collect then iterate.
  • forEach on a sequential stream always preserves encounter order.

Visual Overview

DIAGRAM
  Parallel forEach:                 Parallel forEachOrdered:
  (no order guarantee)              (encounter order preserved)
  ┌────────────────────┐            ┌────────────────────┐
  │ Thread-1: CUR3001  │ prints     │ Thread-1: CUR3001  │ waits
  │ Thread-2: SAV2001  │ prints     │ Thread-2: SAV2001  │ prints first
  │ Thread-3: SAV2002  │ prints     │ Thread-3: SAV2002  │ waits for T-2
  │ Output: scrambled  │            │ Output: ordered    │
  └────────────────────┘            └────────────────────┘

Key Takeaway

forEach and forEachOrdered both execute a Consumer on each element. In sequential streams, both preserve order. In parallel streams, forEach is non-deterministic (faster), forEachOrdered preserves encounter order (slower).

Prefer collect(toList()) over forEachOrdered for ordered results from parallel streams. collect is designed for parallel accumulation with order preservation.

Remember one sentence: forEach is unordered in parallel (fast), forEachOrdered preserves order (slower), and collect(toList()) is the best of both worlds.

---

Share
Use ← → arrow keys to navigate