Two different jobs that look alike
In the previous guide we drew a sharp line between two jobs that both end in "slow the sender down," yet protect completely different things. Flow control protects the receiver: it stops a fast sender from overrunning a slow listener's buffer, and it works using the receive window the receiver advertises in every acknowledgment. Congestion control protects the network: it stops all the senders together from overrunning the routers in the middle, where buffers fill and packets start getting dropped. The receiver might have gigabytes of room free and the network can still be choking — the two limits are independent.
The hard part is that no single sender can see the network. There is no traffic cop, no central dashboard showing how full the routers are. A sender on a laptop in Taipei has no idea that ten thousand other senders are pouring into the same overloaded link in Frankfurt. So TCP has to do something almost magical: infer the state of a shared resource it cannot directly observe, using only the faint clues that come back from the far end. The cleverness of congestion control is almost entirely about reading those clues correctly.
The day the Internet almost seized up
To see why we need congestion control at all, imagine the network with it switched off. Every sender simply blasts packets as fast as its own link allows, and resends anything that gets lost. Now overload a router: packets pour in faster than its outgoing link can drain, the buffer fills, and the router does the only thing it can — it drops the overflow. Each dropped packet eventually times out at its sender, which dutifully retransmits it. But the link is already saturated, so the retransmission just adds more load, causing more drops, causing more retransmissions. The harder everyone pushes, the less actually gets through.
This vicious spiral has a name: congestion collapse. It is the state where the network is busy moving packets at full tilt, yet the useful goodput — bytes that actually arrive and are accepted by the receiver, not duplicates or doomed retransmissions — crashes toward zero. This is not a thought experiment. In October 1986 a stretch of the early Internet between two sites a few hundred metres apart collapsed exactly this way: throughput fell by a factor of about a thousand, from 32 kbps down to 40 bps. That scare is what motivated Van Jacobson's congestion-control algorithms, the ancestors of everything in this rung.
Reading the network's mind through loss
If a sender cannot see the routers, what clue can it read? Jacobson's insight was beautifully indirect: on a healthy wired link, packets almost never get lost for random reasons. So when a packet does go missing, the overwhelmingly likely cause is that some router's buffer overflowed — in other words, the network is congested. TCP therefore treats packet loss as the network's way of saying "slow down." Loss is the implicit feedback signal, even though no router ever sends an explicit message.
TCP detects that loss in two ways, which the next two guides develop in full. The blunt one is the round-trip-time-based timer: if an acknowledgment for a segment hasn't arrived within the expected time, assume it's lost. The sharper one is duplicate acknowledgments — when the receiver keeps re-acking the last in-order byte it has, that repetition is a hint a single packet went missing while later ones arrived. Either way, the sender concludes congestion and reacts. For this guide the detection mechanism matters less than the reaction, which is governed by one elegant control law.
The congestion window: a self-imposed speed limit
How does a sender actually "slow down"? It keeps a private number called the congestion window, usually written cwnd, measured in bytes (or, for intuition, in packets). The rule is simple: the sender may have at most cwnd bytes of data in flight — sent but not yet acknowledged — at any moment. A small cwnd means a trickle; a large cwnd means a flood. Crucially the receiver never sees cwnd and never sets it; it is the sender's own self-imposed ceiling, its guess about how much the network can currently take.
Recall that flow control already gave the sender a ceiling: the receive window, rwnd. So the sender obeys the smaller of the two at every instant: the amount it is allowed in flight is min(cwnd, rwnd). The receive window protects the listener; the congestion window protects the network; the sender honours whichever is tighter right now. The entire art of congestion control reduces to one question: how should the sender raise and lower cwnd over time, knowing only when packets get through and when they get lost?
AIMD: speed up gently, brake hard
The answer is the heart of this guide: AIMD, short for Additive Increase, Multiplicative Decrease. The rule is two-sided and deliberately lopsided. While packets are getting through, the sender raises cwnd by a small fixed amount — about one packet's worth per round trip. That is the additive increase: a slow, linear, polite probe upward, asking "can the network take a little more?" But the instant it sees a loss, it does not nudge cwnd down — it slashes it, typically by half. That is the multiplicative decrease: a sudden, sharp retreat.
Think of AIMD as a careful driver on a foggy road. They accelerate gently, a little faster each second, feeling for the edge of what's safe. The moment they sense danger — a loss — they brake hard, dropping speed by half, then begin the gentle acceleration all over again. Plot cwnd against time and you get the famous TCP sawtooth: a slow linear climb, a sheer cliff down, climb, cliff, climb, cliff, forever. That endless probing is not a bug; it is how TCP keeps re-measuring a network whose capacity is changing every second as other flows come and go.
cwnd over time under AIMD (each '/' = additive increase per RTT, each vertical drop = halving on a detected loss): cwnd | /| /| /| | / | / | / | | / | / | / | | / v / v / v | / | / | / | |/ |/ |/ | +----------------------------------------> time additive increase ^ loss: cut cwnd in half (multiplicative decrease)
Why the lopsidedness — and why it's fair
Why add slowly but cut violently? Because the two errors are not equally dangerous. Overshooting capacity restarts the collapse spiral we saw earlier, so the safe move is to retreat fast and decisively the moment trouble appears. Undershooting merely wastes a little bandwidth, which the gentle additive climb quietly recovers. AIMD is built to fail safe: when unsure, give the network breathing room. The asymmetry is the whole point, not an accident of tuning.
There's a deeper payoff, and it is almost surprising: AIMD is fair. Picture two flows sharing one bottleneck link, starting at wildly different rates. Each round, both add the same fixed amount, and on a shared loss both cut by the same fraction. Adding equal amounts doesn't change their gap, but cutting by the same proportion shrinks the gap every time — the bigger flow loses more in absolute terms. Run this forward and the two rates drift toward equality on their own, with no negotiation, no central referee. That self-organizing fairness is what TCP fairness means, and AIMD delivers it almost for free.
Two honest caveats keep this realistic. First, AIMD only converges to fairness among flows that share a bottleneck and roughly share an RTT; a flow with a much shorter round trip climbs faster and grabs a larger share, so "fair" is approximate, not exact. Second, AIMD as described is the steady-state behaviour, the congestion-control cruise. It is not how a connection starts — a brand-new connection has no idea what the link can take, so it uses a faster ramp called slow start, and that is exactly where the next guide picks up the story.