Skip to main content

Retransmission and Timers

Reliable delivery means noticing loss and resending. TCP has two ways to notice: a timeout (nothing came back in time) and duplicate ACKs (something came back, but it is telling you the same thing repeatedly). This lesson covers how TCP sets that timeout, why it must adapt as the network changes, and the two related timing behaviors that most often surprise people in practice - Nagle's algorithm and delayed ACK.

Estimating RTT: SRTT and RTTVAR

TCP cannot use a fixed timeout - a LAN round trip is under a millisecond, a transcontinental one is well over 100ms, and either can change mid-connection. Instead it maintains a running estimate, updated on every non-retransmitted ACK using an exponentially weighted moving average (the Jacobson/Karels algorithm):

SRTT = (1 - alpha) * SRTT + alpha * SampleRTT
RTTVAR = (1 - beta) * RTTVAR + beta * |SRTT - SampleRTT|

SRTT (smoothed RTT) tracks the average round trip. RTTVAR tracks how much recent samples have jumped around it. Standard constants are alpha = 1/8 and beta = 1/4 - chosen as powers of two so the whole computation can be done with integer shifts instead of floating-point division.

Computing RTO

The retransmission timeout is built from both numbers, not just the average:

RTO = SRTT + 4 * RTTVAR

The 4 * RTTVAR term matters: a path with a stable RTT can use a tight timeout, but a path whose RTT jitters needs slack, or ordinary variance keeps tripping the timer into false retransmits. RFC 6298 also floors RTO at 1 second and requires it to at least double on every consecutive timeout (exponential backoff), capping only at an upper bound (commonly 60s).

Concrete example. Suppose SRTT = 100ms, RTTVAR = 20ms:

RTO = 100 + 4*20 = 180ms

A sample of 150ms arrives (higher than average, path got a bit slower):

RTTVAR = 0.75*20 + 0.25*|100-150| = 15 + 12.5 = 27.5ms
SRTT = 0.875*100 + 0.125*150 = 87.5 + 18.75 = 106.25ms
RTO = 106.25 + 4*27.5 = 216.25ms

RTO grew because the variance grew, even though SRTT barely moved. If the next several samples come back near 106ms, RTTVAR decays back down and RTO tightens again. This is the mechanism, not just the average, that lets TCP shrink its timeout on a quiet, stable path and stretch it on a jittery one.

Karn's algorithm: why retransmits don't count

If a segment is retransmitted, which ACK does a later reply belong to - the original transmission or the retransmit? There is no way to tell from the ACK alone (this is the retransmission ambiguity problem). Using the wrong one poisons the RTT estimate in either direction: crediting a slow retransmit as if it were fast makes RTO shrink dangerously; crediting a fast retransmit's ACK to the slow original inflates it needlessly.

Karn's algorithm resolves this by refusing to guess: never use a sample from a segment that was retransmitted to update SRTT/RTTVAR at all. Only clean, non-retransmitted round trips feed the estimator. (A companion rule, Karn's backoff, keeps the exponentially-backed-off RTO from a timeout in place across the very next segment, rather than snapping straight back down.)

Two ways to detect loss

SignalWhat it meansReaction
RTO expiresno ACK at all arrived in time - possibly heavy congestion or a dead pathretransmit, reset ssthresh, restart from slow start (see Congestion Control)
3 duplicate ACKslater data is arriving and being ACKed, but one segment is still missingfast retransmit the missing segment immediately, without waiting for RTO

A duplicate ACK means the receiver got something out of order and is re-announcing the same "next expected byte." One duplicate can just be reordering; three duplicates (the original ACK plus three repeats) is strong enough evidence of an actual gap that TCP retransmits right away rather than waiting out the full timeout - this is fast retransmit, and it is almost always faster than waiting for RTO to expire.

Delayed ACK

Sending a bare ACK for every single segment is wasteful, so a receiver may hold an ACK briefly (commonly up to 200ms, or "wait for one more segment") to see if it can piggyback the ACK on outgoing data or fold two ACKs into one. This reduces small-packet overhead on request/response and bulk transfers alike.

Nagle's algorithm

Nagle's algorithm targets a different waste: an application that writes data one byte (or a few bytes) at a time - an interactive terminal, for example - would otherwise generate a flood of tiny segments. Nagle's rule: if there is already unacknowledged data in flight, buffer further small writes until either a full segment's worth accumulates or the outstanding data is ACKed. It trades a little latency for far fewer segments.

Nagle + delayed ACK is the classic stall

Put the two together and they can deadlock on latency. The sender, following Nagle, will not send its next small write until the previous one is ACKed. The receiver, following delayed ACK, is in no hurry to send that ACK and waits to see if more data or a response is coming. Neither side acts, so every write stalls for the full delayed-ACK timeout (often ~200ms) before the next byte moves - a very visible, very reproducible latency bug on request/response traffic with small writes. The standard fix is to disable Nagle on the socket (TCP_NODELAY) for latency-sensitive, small-message workloads such as RPC and interactive protocols, leaving delayed ACK alone.

info

TCP_NODELAY is the socket option most engineers reach for after chasing an unexplained ~40ms or ~200ms floor on small request/response calls. If disabling it fixes the latency, Nagle and delayed ACK were very likely the cause.