Fan-out fan-in is a two-phase concurrency pattern: fan-out splits a unit of work into many independent pieces and dispatches them in parallel, and fan-in waits for those pieces to finish and merges their results back into a single outcome. It is one of the most common shapes in concurrent programming precisely because so many real problems decompose the same way — call ten APIs and combine the responses, process a thousand files independently, run the same query across a dozen shards.
What changed in 2026
- It became the default shape for orchestrating parallel AI and agent calls, including batched LLM requests during large AI-driven code migrations, where dozens of independent chunks are processed concurrently and then reassembled.
- Managed workflow and orchestration tools made it a built-in primitive, rather than something every team hand-rolls with raw threads or callbacks.
- Backpressure-aware fan-out became the expected default, not an afterthought — bounding concurrency and rate-limiting the calls a fan-out makes is now treated as part of implementing the pattern correctly, not an optional hardening step added later.
The shape: distribute, then collect
A fan-out fan-in operation has three parts: a splitting step that defines the independent units of work, a parallel execution step where each unit runs concurrently without depending on the others, and a collection step that waits for all units to complete and combines their results in a defined way — concatenation, aggregation, or picking a winner, depending on the problem. The units must be genuinely independent; if one unit's work depends on another's result, that dependency belongs in a pipeline, not a fan-out.
Implementation patterns by ecosystem
| Ecosystem |
Fan-out mechanism |
Fan-in mechanism |
| Go |
Goroutines |
A channel collecting results, or a sync.WaitGroup |
| JavaScript / Node |
Multiple promises started concurrently |
Promise.all or Promise.allSettled |
| Python |
asyncio tasks |
asyncio.gather |
| Message-queue based |
Multiple worker processes consuming a queue |
A results queue or database row per completed unit |
The hard part: partial failure
The happy path — every unit succeeds and fan-in merges clean results — is the easy part to write. The real design decision is what happens when some units fail and others succeed. Promise.all-style collection stops at the first failure by default, which can discard work that already completed successfully. Promise.allSettled and its equivalents in other languages collect every outcome, success or failure, and leave the decision about partial results to the caller — usually the better default when the units are independent and partial success is still useful.
Bounding concurrency
Fanning out to ten units is harmless. Fanning out to ten thousand units against an external API or a shared database without a cap is how a batch job takes down the thing it depends on. A concurrency limit — process at most N units at a time, regardless of how many are queued — keeps the fan-out from overwhelming a downstream dependency, and a token bucket in front of an external call adds a second layer of protection when the downstream target has its own rate limit to respect.
Common pitfalls
No concurrency cap. Launching every unit of work at once assumes unlimited downstream capacity, which is rarely true. Cap it explicitly, even if the cap feels conservative at first.
Losing errors silently. A fan-in step that only returns successful results without surfacing which units failed and why makes debugging a partial failure far harder than it needs to be.
Treating fan-out as free parallelism. Spinning up thousands of goroutines or tasks has real memory and scheduling overhead; a bounded worker pool is usually more efficient than unbounded concurrent units for very large fan-outs.
FAQ
How is fan-out fan-in different from a simple pipeline?
A pipeline's stages depend on each other in sequence; fan-out fan-in's units run independently and in parallel, with no unit depending on another's output. The two are often combined, with a pipeline stage internally implemented as a fan-out.
What is the right concurrency limit?
It depends entirely on what the workers call — a database's connection pool size, an API's documented rate limit, or a server's CPU count are common starting points, not an arbitrary number picked in isolation.
Is fan-out fan-in the same as MapReduce?
They are closely related. MapReduce is a specific, large-scale framework built around the same fan-out-then-aggregate shape, with additional structure for distributed storage and fault tolerance.
Should I always use allSettled instead of all?
Not always — if any single failure should abort the whole operation, all-style fail-fast behavior is correct. Use the settle-everything approach when partial success is still useful to the caller.
Where to go next