How long is too long? Estimating the RTT
In guide 3 we watched the congestion window breathe — ramping up in slow start, climbing gently in congestion avoidance, and getting cut when loss appears. But that whole machine hinges on one quiet question we skipped: how does the sender know a packet was lost in the first place? The honest answer is that the network never sends a 'your packet died' message. The sender has to infer loss, and its main clue is silence — an acknowledgment that should have come back, but didn't. So the very first thing we need is a sense of how long to wait before that silence counts as loss.
That waiting period is the retransmission timeout (RTO), and setting it is a real balancing act. Set it too short and the sender panics over packets that were merely slow, resending data that is still in flight and wasting capacity. Set it too long and the sender sits idle after a genuine loss, letting the link go quiet. The trick is to anchor the timeout to the connection's measured round-trip time (RTT) — the time from sending a segment to getting its ACK back — because the RTO should be a little longer than a normal round trip, just enough to be sure the ACK isn't simply on its way.
But RTT is a moving target: queues fill and drain, routes shift, so each round trip differs. TCP doesn't trust any single measurement. It keeps a running smoothed average of the RTT and, just as importantly, a running estimate of how much the RTT jitters — its variation. The timeout is then built roughly as the smoothed RTT plus a healthy multiple of that variation. On a steady link the timeout hugs the average; on a jittery link it automatically grows a wider safety margin so a normal spike doesn't trigger a needless resend. The network teaches TCP its own rhythm.
The fast lane: duplicate ACKs and fast retransmit
Waiting for a timeout to fire is the slow path to spotting loss — a full RTO can be many times longer than the RTT, and on top of that the timer's expiry is treated as the worst kind of loss signal, collapsing the window all the way back into slow start. That is a brutal penalty for a single dropped packet. TCP wants a quicker, gentler way to notice that one segment fell out, and it finds it hidden in the receiver's own chatter.
Recall from the transport rung that TCP's ACKs are cumulative: an ACK names the next byte the receiver wants. When a segment goes missing but later segments keep arriving, the receiver can't advance — so it repeats the same ACK number every time another out-of-order segment lands, each one a small cry of 'I'm still waiting for byte N.' If the sender sees three of these duplicate ACKs for the same byte (three, so a little reordering doesn't fool it), that's a strong signal: the segment at N was lost, but traffic after it is still flowing. So the sender resends that one segment right away, without waiting for the timer. This is fast retransmit.
sender bytes: 1001 2001 3001 4001 (each segment = 1000 bytes)
X (1001-2000 LOST)
receiver gets: 2001 3001 4001
receiver ACKs: ack1001 ack1001 ack1001 ack1001
^ ^---- 3 duplicate ACK 1001s ----^
original sender: "1001 is gone" -> RESEND 1001
(no need to wait for the timeout)Braking gently: fast recovery
Fast retransmit fixes the loss quickly, but there's a second decision: how hard should the window brake? In guide 2 we met the AIMD law — additive increase, multiplicative decrease, the careful driver who speeds up gently and brakes hard. A timeout is the hard brake: the network may be in real trouble, so the window slams back to one and restarts slow start. But three duplicate ACKs tell a different story. The fact that the receiver is still sending ACKs at all means packets are still getting through — the pipe is congested, not collapsed. Treating that like a total outage would be an overreaction.
So fast recovery takes the softer path. Instead of dropping to one and re-entering slow start, the sender halves its window (the multiplicative decrease) and then continues straight into congestion avoidance from that halved level — the gentle additive climb. This pairing of fast retransmit with fast recovery is the heart of TCP Reno, the classic variant that turned the brutal sawtooth into a far smoother one: a single loss now costs you half your window, not all of it.
The picture to hold is two kinds of loss with two kinds of response. A few duplicate ACKs mean 'one packet slipped through a busy queue' — fast retransmit plus fast recovery, halve and keep cruising. A full timeout means 'I've heard nothing at all' — that could be a severe jam or a broken path, so retreat all the way to slow start and rebuild from scratch. Same goal in both cases, but the severity of the silence sets the severity of the brake.
Modern engines: Reno, CUBIC, and BBR
Reno's gentle linear climb was fine for the networks of the 1990s, but it ages badly on today's fat, long links. The reason is arithmetic: after a loss, additive increase adds roughly one segment per RTT, so a connection across a high-speed continent-spanning path can take a very long time to crawl its window back up to where the link is actually full. The link's capacity sits unused while Reno politely inches upward. Modern variants exist mostly to fix this slow-fill problem.
TCP CUBIC is the default on Linux and Android, so it carries an enormous share of the world's traffic. Its idea is clever: instead of growing the window as a straight line in time, it grows it along a cubic curve. Right after a loss it ramps back up quickly toward the window size it had before, then flattens out cautiously as it approaches that remembered ceiling, then — if no loss appears — probes gently above it for more room. The shape means CUBIC refills a fat pipe far faster than Reno while still easing off near the danger zone. Crucially, CUBIC is still loss-based: it reads a dropped packet as the signal that the pipe is full.
BBR, from Google, breaks that whole tradition. Instead of waiting for loss, it actively measures two things — the link's available bandwidth and its minimum round-trip time — and aims to send at exactly the rate that keeps the pipe full but the queues nearly empty. It is rate-based and model-based rather than loss-based. The payoff is that BBR can ignore the random, non-congestion losses that fool CUBIC and Reno, which is a big deal on lossy wireless paths. The honest caveat: because BBR doesn't back off on loss the way loss-based flows do, mixing it with CUBIC in the same bottleneck can be unfair, and getting that balance right is still active engineering.
When loss lies: wireless and what comes next
Everything loss-based hangs on one assumption: that a dropped packet means a full queue, i.e. congestion. On wired links that's usually true. But on a wireless link, a packet often dies to radio interference, a weak signal, or a moment of fading — nothing to do with congestion at all. Classic TCP can't tell the difference, so it reads the loss as 'the network is full' and brakes hard, dragging throughput down on a link that was actually fine. This is the central reason loss-based congestion control misfires over Wi-Fi and cellular, and a large part of why BBR's measure-don't-guess approach is so appealing there.
There's a deeper truth lurking here. Loss is a late and lossy signal — by the time a packet drops, a queue somewhere has already filled and emptied, and other flows have already felt the delay. A more honest signal would be the rising delay itself, or an explicit mark from the router before its buffer overflows. That second idea has a name we'll meet next: Explicit Congestion Notification lets a router set a flag saying 'I'm getting full' so senders can ease off without anything being lost at all. It's congestion control's move from inferring trouble to being told about it.
Step back and the arc is clear. We started this rung separating flow control (protect the slow receiver) from congestion control (protect the shared network), met congestion collapse and the AIMD cure, watched slow start and the sawtooth, and here tuned the timeout, sped up loss detection with fast retransmit, softened the brake with fast recovery, and surveyed the modern engines. The last guide closes the loop: bufferbloat, the router-side help of ECN and active queue management, and the question that ties it all together — when many flows share one link, is the split fair?