// lesson: pipeline-surgery
Pipeline Surgery
Pipelines compose when every stage honors the same contract: consume until the input closes, close your outputs when you're done, never strand a goroutine. Two plumbing fittings show up constantly:
- Tee splits one stream into two, like the shell command โ every value goes to both outputs.
- Bridge flattens a channel of channels (
<-chan <-chan T) into one stream (<-chan T): read the next inner channel off the outer one, drain it completely, then move to the next. It shows up when a producer hands you a fresh channel per unit of work โ pagination, one channel per shard โ and consumers just want one linear stream instead of juggling N.
This lesson's challenge below is Tee only; Bridge is here so the name isn't a surprise if you meet it in the wild.
Tee has a sharp edge: you must send each value to both outputs, but the two
consumers run at different speeds, and you can't just send to one then the
other โ if the first consumer stalls, the second starves even though it's
ready. The idiomatic move is the nil-channel trick: in a loop, select
over both sends, and after a send succeeds set that channel variable to
nil so its case goes dormant until the next value:
o1, o2 := out1, out2
for i := 0; i < 2; i++ {
select {
case o1 <- v:
o1 = nil
case o2 <- v:
o2 = nil
}
}
Note the honest consequence: tee advances one value at a time, so the fast consumer can run at most one value ahead of the slow one. That's correct โ it's backpressure, not a bug. What is a bug: forgetting to close both outputs, or leaving the forwarding goroutine alive after the input closes.
โบ Tee
35 ptsImplement:
func Tee(in <-chan int) (<-chan int, <-chan int)
Required semantics:
- Every value received from
inis delivered to both outputs, each in the original input order. - It must work with the two consumers running at different speeds, as long as both are actively consumed. (Lockstep coupling โ the fast output waiting at most one value for the slow one โ is expected and fine.)
- When
incloses, both outputs close after delivering everything, including the zero-value case whereincloses immediately. - No leaks: after a full tee cycle completes, every goroutine you
started must have exited. The tests run several cycles and compare
runtime.NumGoroutine()before and after with settling time.
Log in to submit a solution and earn points.