Two ways to drown a connection
In the transport rung you watched TCP turn a lossy, reordering network into a perfect byte stream: it numbers every byte, acknowledges what arrives, and retransmits what does not. But reliability raises a question it does not answer on its own. At what speed should the sender pour bytes in? Too slow and you waste the link; too fast and bytes pile up somewhere and get dropped. There turn out to be two completely separate places where they can pile up, and TCP needs a different mechanism for each.
The first place is the receiver itself. Imagine pouring water from a jug into a cup. The cup is the receiver's buffer — a fixed chunk of memory where arriving data waits until the application reads it. If the sender pours faster than the application drinks, the cup overflows and data is lost. Guarding against this is flow control, and its whole concern is one slow endpoint: protect the receiver from a sender that is too fast for it.
The second place is the network in between — the routers and links the bytes must cross. Picture a four-lane highway narrowing to one lane: that pinch point is the bottleneck link. If everyone sending through it collectively offers more traffic than the bottleneck can carry, packets queue in a router's buffer, the queue fills, and packets are dropped. Guarding against this is congestion control, and its concern is shared: protect the network, which belongs to no single connection, from the combined weight of everyone using it at once.
Flow control: a number the receiver hands you
Flow control is the easy one, because the receiver knows the answer and can just say it. On every acknowledgment it sends back, the receiver includes a field called the receive window (rwnd): the number of bytes of free space left in its buffer right now. The sender obeys a simple rule — never have more unacknowledged data in flight than the latest advertised window. If the receiver says 'I have 8 KB of room,' the sender may have at most 8 KB outstanding and must then wait for an acknowledgment before sending more.
This is exactly the sliding window you met in the link and transport rungs, now driven by the receiver's free space. As the application drinks from the cup, free space opens up; the next acknowledgment advertises a larger window; the sender's window slides forward and it may send more. The feedback loop is tight and honest because the only party who knows the cup's level — the receiver — is the one reporting it. There is no guessing involved. This mechanism, TCP flow control, was covered in detail in the previous rung; here we only need it as the contrast that makes congestion control's difficulty visible.
Notice what flow control is blind to. The receive window says nothing about the highway in the middle. A receiver with a generous 64 KB cup will happily advertise a huge window, and a sender that obeyed only flow control would gleefully blast 64 KB into a path whose bottleneck can carry a tiny fraction of that. Flow control would be perfectly satisfied while the network in between melts down. Protecting the receiver and protecting the network are simply two different jobs.
Congestion control: guessing at a number nobody tells you
Congestion control needs its own limit, and TCP keeps a second, separate budget for it: the congestion window (cwnd). It is the sender's private estimate of how much data the network path can hold without overflowing some router's queue. The sender then obeys both limits at once. The amount it is allowed to have in flight is the smaller of the two windows, which we can write as min(rwnd, cwnd). Flow control caps it at the receiver's room; congestion control caps it at the network's room; whichever is tighter wins.
Allowed bytes in flight = min( rwnd , cwnd ) rwnd (receive window) : the RECEIVER tells you this explicitly cwnd (congestion window) : the SENDER must GUESS this from feedback rwnd small -> slow receiver is the limit (flow control) cwnd small -> busy network is the limit (congestion control)
Here is the cruel asymmetry. rwnd arrives as a clean number in every acknowledgment. cwnd has no such source — no router ever sends a packet saying 'I am 90 percent full, ease off.' (Not by default; later in this rung you will meet ECN, an opt-in way for routers to whisper exactly that, but classic TCP runs without it.) So the sender must infer the network's state from the only signals it can observe: which packets get acknowledged, how quickly, and which seem to vanish. Congestion control is the art of estimating an invisible limit from indirect clues.
The classic inference is brutally simple: lost packets mean the network is full. The reasoning is that on a wired path almost the only reason a packet disappears is that a router's queue overflowed and dropped it, so a loss is a near-certain sign of congestion. When TCP detects a loss it shrinks cwnd hard; when packets keep arriving it grows cwnd gently to probe for more room. That gentle-up, sharp-down pattern is the heart of the next guide, where it becomes the AIMD law and the famous TCP sawtooth.
Why this matters: the threat of collapse
It would be easy to dismiss congestion control as a mere efficiency tweak. It is not — it is what keeps the Internet standing up. Recall that the network has no central traffic cop deciding who may send and how much; every sender decides for itself. If senders simply ignored congestion and kept retransmitting lost packets at full speed into an already-jammed bottleneck, something genuinely catastrophic can happen, and historically did happen in 1986: congestion collapse.
Collapse is a vicious feedback loop. The bottleneck is overloaded, so packets are dropped. Senders treat the loss as 'my packet got lost, I must resend it' and retransmit — adding even more traffic to the link that was already overfull. Now even more is dropped, triggering even more retransmissions. The link stays maxed out, but almost everything crossing it is a doomed duplicate that will be dropped again. Useful throughput — actual fresh data delivered, what we call goodput — collapses toward zero even though the wire is 100 percent busy. The network is working as hard as it possibly can while accomplishing almost nothing.
This is the crucial honesty of the subject: the Internet stays up not because some authority forces order, but because the endpoints voluntarily cooperate. TCP's congestion control is the gentleman's agreement that every well-behaved sender backs off when the network looks loaded. There is no policeman — there is only a shared discipline running on billions of machines. The next guide tells the story of collapse and the AIMD idea that rescued the Internet from it.
A map of the rest of this rung
Now that the two problems are cleanly separated, here is the road ahead — each name is something we will genuinely unpack, not just label. The next guide tells the story of collapse in 1986 and the cure: AIMD, add a little when things go well, cut hard when they go wrong, which produces TCP's signature sawtooth and is also what makes connections share a link fairly.
- Slow start and congestion avoidance: how a brand-new connection ramps up — doubling cwnd each round-trip to find the path's capacity fast, then switching to cautious linear growth once it gets close, drawing the sawtooth in full.
- Detecting trouble in time: estimating the round-trip time to set a sensible retransmission timeout, and the cleverer fast retransmit and fast recovery that react to loss without waiting for a slow timer to fire.
- Modern variants: how Reno, then CUBIC, then BBR each redefine what 'congested' even means — CUBIC reshaping the growth curve for today's fast links, and BBR abandoning loss entirely to measure bandwidth and delay directly.
- Help from the routers, and its dark side: ECN letting a router mark a packet instead of dropping it, active queue management like RED and CoDel, the bufferbloat problem where over-large buffers wreck latency, and what fairness really means.
One honest warning to carry forward into all of it. The classic logic 'loss equals congestion' is an assumption, not a law, and it has a famous failure mode. On a wireless link — Wi-Fi, cellular — packets are sometimes corrupted and lost by radio interference, fading, or a passing obstacle, with no congestion at all. Loss-based TCP misreads that bit-error loss as a full network and needlessly slams the brakes, so a perfectly idle wireless path can deliver far less than it should. Keep that misfire in mind; it is exactly the kind of gap that variants like BBR were designed to close.
The careful driver
If you keep one picture from this guide, make it this. TCP's congestion control behaves like a careful driver on a road they cannot fully see. It speeds up gently — a little more throttle each moment things go smoothly — and it brakes hard the instant it senses trouble, because a gentle brake into a real jam would be a pile-up. It never knows the true speed limit, the invisible capacity of the path; it can only feel its way toward it, ease past it until something pushes back, then ease off. The whole rest of this rung is the engineering of that one instinct.