backpressure in async pipelines
Stand at the end of an assembly line and imagine the first station slapping items onto the belt twice as fast as the last station can box them up. Items pile up between stations; eventually they spill off the belt onto the floor. The factory's answer is a signal that travels backward: when the boxing station is swamped, it tells the station before it to slow down, which tells the one before it, all the way to the start. Backpressure is exactly that backward 'slow down' signal in a concurrent pipeline: a way for an overwhelmed consumer to push back on a too-fast producer.
Concretely, a producer task feeds items to a consumer task, usually through a channel or queue. Without backpressure, if the producer is faster, the items waiting in the channel grow without bound — an unbounded queue silently turning into a memory leak, then an out-of-memory crash, with latency ballooning along the way. Backpressure caps this by making the channel bounded: when the buffer is full, the producer's send operation blocks or suspends (it awaits) until the consumer dequeues something and frees a slot. That await is the back-pressure — the producer is literally made to wait, so its rate is throttled to match the consumer's. The mechanism propagates up a chain of stages: a slow final stage fills its inbox, which stalls the stage before it, which stalls the one before that, so the whole pipeline self-regulates to the speed of its slowest link. Other strategies exist (drop oldest, sample, or explicitly request N items as in reactive-streams 'demand'), but bounded-buffer blocking is the common default.
Backpressure matters because it is the difference between a system that degrades gracefully under load and one that falls over. Without it, a traffic spike does not just slow you down — it exhausts memory and crashes. With it, the system stays bounded: it slows, it may shed or buffer, but it survives. The honest caveat that beginners miss: backpressure does not make a slow consumer fast. It bounds resource use and surfaces the bottleneck, but the fundamental limit is still the slowest stage; if data genuinely arrives faster than you can ever process it, backpressure forces a real decision — block the source, drop data, or scale out — rather than pretending and crashing later.
A bounded channel applies backpressure: tx.send(item).await blocks once 32 items sit unconsumed, throttling the producer to the consumer's rate. An unbounded channel would instead let the backlog grow until out-of-memory.
A full bounded buffer makes the producer await; that wait is the backpressure, throttling it to the consumer.
Backpressure bounds resource use; it does not speed up a slow consumer. The real limit is the slowest stage. If input genuinely outpaces processing forever, you must choose to block, drop, or scale out — there is no way to absorb an unbounded rate in bounded memory.