Stage 1 · Code
Concurrency in Practice
Pipelines, Fan-In & Fan-Out
Some concurrent programs are easiest to understand as stages: one stage produces values, the next transforms them, and the next filters or combines them. This lesson turns that mental model into real Go code.
Thinking in Stages
A pipeline is just a chain of stages. Each stage has one job: receive values from an input channel, do one piece of work, and send the result to an output channel. Instead of one giant function doing everything, the work is broken into a flow you can follow step by step.
That structure is useful when a problem naturally happens in phases. Maybe you read files, then parse them, then validate them, then write summaries. Or maybe you generate numbers, transform them, and then filter them. Each stage owns one responsibility, which keeps concurrent code easier to reason about.
generate numbers -> square each number -> keep only even results -> print final valuesEach arrow is a channel handoff. One stage finishes a piece of work and passes the value to the next stage instead of trying to do every phase itself.
Fan-Out for Slow Stages
What if one stage is slower than the others? Maybe squaring numbers is easy in our toy example, but in a real program that stage might mean resizing images or calling a remote API. If one stage becomes the bottleneck, you can fan out that stage by running several goroutines that all read from the same input channel.
This works because receiving from one shared input channel naturally divides the work. Each value is received by exactly one of the workers. That gives you parallel help on the slow stage without changing the surrounding pipeline structure.
Fan-In to Merge Results
Once you fan out a stage, you usually need to bring the outputs back together so the next stage can treat them as one stream again. That merge step is called fan-in. In plain language, fan-out splits one input stream across several workers, and fan-in merges several output streams back into one channel.
t=0 generate sends 1, 2, 3, 4, 5, 6 and then closes
t=1 square worker A receives 1, square worker B receives 2
t=2 worker A sends 1, worker B sends 4 into merge
t=3 worker A receives 3, worker B receives 4
t=4 worker A sends 9, worker B sends 16 into merge
t=5 worker A receives 5, worker B receives 6
t=6 worker A sends 25, worker B sends 36 into merge
t=7 filterEven keeps 4, 16, 36 and drops 1, 9, 25The exact interleaving can change, but the structure stays the same: upstream stage produces, middle stage fans out, merge fans in, final stage filters.
Each stage knows when it is finished sending on its output channel, so each stage should close that output itself. The merge stage is a small coordinator, so it closes the merged output only after every forwarding goroutine is done.
Quiz and Practice
What makes pipeline code easier to reason about?
Hands-On Project
This challenge asks you to build the small three-stage pipeline from the lesson and return the final values as a slice.
Write a function `EvenSquares(nums []int) []int` that builds a pipeline with three stages: generate the numbers from the slice, square them, and keep only the even squares. Return the final values in the order produced by this single-worker pipeline.
Summary and Key Takeaways
- A pipeline is a series of stages, each reading from an input channel and writing to an output channel.
- Breaking work into stages can make concurrent code easier to reason about than one large all-purpose goroutine.
- Fan-out means multiple goroutines share one input channel to parallelize a slow stage.
- Fan-in means merging several output channels back into one stream for the next stage.
- Correct channel ownership and closure are what let pipeline stages finish cleanly instead of hanging.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.