Under The Hood
backend java JDK 21+ (ForkJoinPool, common pool)

ForkJoinPool: Work-Stealing Deque Queues & Parallel Streams

Last updated
Prerequisites:
Java threads and executor basics
Understanding of divide-and-conquer algorithms
  • java
  • forkjoinpool
  • work-stealing
  • parallel-streams
  • concurrency
  • deque

Read at your depth

The practical view

ForkJoinPool is an executor designed for divide-and-conquer and parallelism. Use it directly: ForkJoinPool pool = new ForkJoinPool(); pool.invoke(new RecursiveTask...()); or use the common pool: IntStream.range(0, n).parallel().map(...).sum(). Parallel streams use the common ForkJoinPool (size = CPU cores - 1). Tasks fork (split work) and join (combine results); the pool uses work-stealing to balance load. Avoid blocking inside parallel stream tasks (a blocked task occupies a worker and can exhaust the pool). Use custom pools for blocking or heavy CPU work with controlled parallelism.

The same idea in other frameworks

react equivalent
N/A — JVM concurrency feature.

Legacy vs modern

Sequential recursive split vs ForkJoin work-stealing

A naive recursive split runs one thread; ForkJoin divides the task and steals work so all cores stay busy.

before → after
Sequential divide-and-conquer
int sum(int[] a, int lo, int hi) {
  if (hi - lo < 1000) { int s = 0; for (int i = lo; i < hi; i++) s += a[i]; return s; }
  int mid = (lo + hi) / 2;
  return sum(a, lo, mid) + sum(a, mid, hi); // ❌ OLD: single-threaded recursion
}
Parallel stream (common ForkJoinPool)
int total = IntStream.range(0, a.length).parallel().map(i -> a[i]).sum(); // ✅ NEW

Interview gotchas

Context

This tests the scheduler mechanics — interviewers want the deque design and the LIFO-vs-FIFO rationale (cache locality vs stealing the largest chunk) that makes the pool efficient.

The mechanical answer

Each worker owns a deque: it pushes forked subtasks on its own head and pops from its head (LIFO). LIFO gives cache locality — the most recently forked task is likely the one with the most context in the cache — and keeps the deque small. When a worker's deque is empty, it steals from another worker's TAIL (FIFO): the tail holds the oldest tasks, which are typically the LARGEST remaining chunks of work, and stealing from the tail (opposite end from the owner's pop) minimizes contention — the owner pops the head while thieves take the tail, so they rarely race on the same slot. The steal uses a CAS on the base index so only one thief wins a slot. This asymmetry is the core of the pool's load-balancing efficiency.

Trap

The naive answer is 'work-stealing just shares a global queue'. Each worker has a private deque and only steals when empty, which keeps the common case lock-free. Another trap: assuming join() idles the worker — it actively helps run other tasks while waiting (helpJoin), which is why join is efficient. Candidates who describe the LIFO/FIFO asymmetry and the CAS steal show real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.