Patterns: concurrent Go · 3 MIN READ

Fan-out and fan-in: merge without assuming order

Coordinate multiple producers and one result stream.

Coordinate multiple producers and one result stream. Fan-out spreads work across producers or workers; fan-in merges their results. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.

Concurrent Go pattern · A MAP OF THE IDEA

  1. Several sources produce independently
  2. Forwarders merge their values
  3. Coordinator closes one shared output

Useful when

  • Independent sources should overlap
  • One consumer should observe combined results

Watch out

  • Early exit can block forwarding goroutines
  • Ordered output needs extra identity and buffering

Implementation & operation cost: Medium

Fit depends on the task

Small tool or CLI4/5
Domain-rich application4/5
Independent distributed services5/5

Editorial suitability, not benchmarks or a universal ranking. Styles describe different axes and can be combined.

01

The design decision

Fan-out spreads work across producers or workers; fan-in merges their results. The merger must know when all producers are done before closing the output. Arrival order generally differs from input order, so retain sequence numbers if the product needs ordered results.

02

A complete Go example

Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.

go
package main

import (
	"fmt"
	"sync"
)

func Merge(inputs ...<-chan int) <-chan int {
	out := make(chan int)
	var wg sync.WaitGroup
	for _, input := range inputs {
		wg.Add(1)
		go func(ch <-chan int) {
			defer wg.Done()
			for n := range ch {
				out <- n
			}
		}(input)
	}
	go func() { wg.Wait(); close(out) }()
	return out
}

func main() {
	a, b := make(chan int, 2), make(chan int, 2)
	a <- 1
	a <- 2
	close(a)
	b <- 3
	b <- 4
	close(b)
	sum := 0
	for n := range Merge(a, b) {
		sum += n
	}
	fmt.Println(sum)
}

// Output:
// 10
Read the code

Each forwarding goroutine signals completion, and only the coordinator closes out. main consumes the full merge, so every producer can finish. The sum is deterministic; the individual values can arrive in several valid orders.

03

Tradeoffs and the simpler alternative

When a consumer may stop early, use context-aware sends and ensure upstream producers also stop. Merely buffering out to a guessed size hides the lifecycle problem. A simple sequential loop is easier when there is no parallel work to overlap.

TAKE THIS WITH YOU

Closes merged output after every producer has finished. When a consumer may stop early, use context-aware sends and ensure upstream producers also stop. Merely buffering out to a guessed size hides the lifecycle problem. A simple sequential loop is easier when there is no parallel work to overlap.

Check your understanding

Which statement best describes Fan-out and fan-in: merge without assuming order?

Go to the source

Progress stays in this browser. No account needed.