skip to content

During a TCP bulk transfer, how does the sender's congestion window respond to three duplicate ACKs compared with a retransmission timeout, and why do they differ?

level: middleimportance: should knowfreq 42%

answer

  1. one loss, two reactions
  2. is the ACK clock still running?
  3. halve and inflate, then deflate
  4. loss window of one segment
  5. FlightSize, not cwnd

basics

~20 s

On the third duplicate ACK a TCP sender halves ssthresh from FlightSize, retransmits and continues in fast recovery at about half its old window. On a retransmission timeout it also halves ssthresh but drops cwnd to one segment and slow-starts again.

solid answer

~40 s

Both reactions set `ssthresh = max(FlightSize/2, 2*SMSS)` (RFC 5681). After **three duplicate ACKs**, fast retransmit resends the missing segment and fast recovery sets `cwnd = ssthresh + 3*SMSS`, adds one SMSS per further duplicate ACK, and deflates to `ssthresh` when new data is acknowledged, so the transfer continues in congestion avoidance at about half speed. After a **retransmission timeout**, `cwnd` drops to the loss window, **one segment**, and slow start climbs back to `ssthresh`. The difference is evidence: each duplicate ACK means a later segment reached the receiver, so data is still leaving the network and the ACK clock is running. A timeout means no ACKs arrived at all, so the sender restarts conservatively.

code

pseudocode · 14 lines
pseudocode
# RFC 5681 window reaction to loss (Reno-style)
on third duplicate ACK:
    ssthresh = max(FlightSize / 2, 2 * SMSS)
    retransmit segment starting at SND.UNA
    cwnd = ssthresh + 3 * SMSS            # inflate
on each further duplicate ACK:
    cwnd = cwnd + SMSS                    # inflate
on ACK of new data while in fast recovery:
    cwnd = ssthresh                       # deflate, congestion avoidance
on retransmission timeout:
    if segment not yet retransmitted by the timer:
        ssthresh = max(FlightSize / 2, 2 * SMSS)
    cwnd = 1 * SMSS                       # loss window
    retransmit, then slow start toward ssthresh

go deeper

for a junior

Recall that three duplicate ACKs trigger fast retransmit and a milder window cut, while a timeout drops the window to one segment.

for a middle

Walk the numbers: ssthresh from FlightSize, inflation by three segments, deflation on new ACK, and slow start from one segment after a timeout.

for a senior

Explain from the ACK clock why the two paths differ, and why transfers that end in a timeout rather than fast recovery suffer disproportionate stalls.

for a principal

Discuss the cost of timeouts to short and tail-latency-sensitive flows, and why loss-recovery improvements such as NewReno aim to keep repairs off the timer.

## The scenario A bulk transfer is in congestion avoidance with about **40 segments** in flight (SMSS 1460 bytes). One segment is dropped at the bottleneck. RFC 5681 gives two very different outcomes depending on how the sender finds out. ## Path A: three duplicate ACKs The segments after the lost one still arrive. The receiver cannot acknowledge past the hole, so it sends duplicate ACKs for the same sequence number. RFC 5681 treats the **third** duplicate ACK as a loss indication: 1. **Reduce the threshold:** `ssthresh = max(FlightSize / 2, 2*SMSS)` = max(20, 2) = **20 segments**. 2. **Fast retransmit:** resend the segment starting at `SND.UNA` without waiting for the timer. 3. **Inflate:** set `cwnd = ssthresh + 3*SMSS` = **23 segments**, because three segments have left the network and sit in the receiver's buffer. 4. **Keep inflating:** add one SMSS for every further duplicate ACK, since each one means another segment has left the network; new data may be sent if `cwnd` and `rwnd` allow. 5. **Deflate:** when an ACK for new data arrives, set `cwnd = ssthresh` = **20 segments** and continue in congestion avoidance. The repair takes about one round trip, and the transfer then continues at half its previous window. This combination of slow start, congestion avoidance, fast retransmit and fast recovery is the behaviour historically called **Reno**. ## Path B: a retransmission timeout If too few segments follow the loss to produce three duplicate ACKs, or the ACKs themselves are lost, the retransmission timer fires instead: 1. `ssthresh = max(FlightSize / 2, 2*SMSS)` = **20 segments**, unless this segment was already retransmitted by the timer, in which case `ssthresh` is held constant rather than halved again. 2. `cwnd` is set to no more than the **loss window**: **one full-sized segment**, regardless of the initial window. 3. The segment is retransmitted and **slow start** runs from one segment: with an ACK per segment, 1, 2, 4, 8, 16, then 20 segments. 4. At `ssthresh` the sender returns to congestion avoidance. The transfer pays the timeout wait itself plus about five round trips of reduced sending before it is back at 20 segments. | | Three duplicate ACKs | Retransmission timeout | |---|---|---| | `ssthresh` | max(FlightSize/2, 2*SMSS) | same, or held if already timer-retransmitted | | `cwnd` right after | ssthresh + 3*SMSS, then inflated | 1 segment (loss window) | | Next phase | congestion avoidance at ssthresh | slow start up to ssthresh | | ACK clock | still running | lost | ## Why the two differ RFC 5681 gives the reasoning directly. A receiver can only generate a duplicate ACK when a segment **arrives**, so each duplicate ACK proves a segment has left the network. The path is still delivering; the ACK clock that releases new segments is intact. Loss is still a sign of congestion, so the window is halved, but restarting from one segment would throw away a working clock. A timeout carries the opposite evidence: nothing came back for a whole RTO. The sender cannot tell whether the path is badly congested or the returning ACKs were lost, and it has no clock to pace new data. Starting again from one segment is the safe choice. ## Edges worth knowing - **FlightSize, not cwnd.** RFC 5681 warns that halving `cwnd` is a mistake: `cwnd` can have grown beyond what is actually in flight, for example beyond `rwnd`, and would set `ssthresh` too high. - **Several losses in one window.** RFC 5681 notes this fast recovery does not recover efficiently from multiple losses in one flight. The ACK for the first retransmission is a **partial acknowledgment**; NewReno (RFC 6582) treats it as a sign the next segment is also lost, retransmits it and stays in fast recovery until the whole original flight is acknowledged. - **Other algorithms.** CUBIC (RFC 9438) keeps fast retransmit and fast recovery but reduces with a factor of 0.7 instead of halving. - **Out of scope here:** how the RTO value is computed, and when a receiver sends duplicate ACKs, belong to the retransmission and acknowledgment mechanisms respectively; this answer covers only what happens to the window.

  • What goes wrong in basic fast recovery when two segments from the same window are lost?
    The ACK for the first retransmission acknowledges only part of the flight, a partial acknowledgment. Basic RFC 5681 fast recovery deflates and leaves, so the second hole may wait for a retransmission timeout. NewReno (RFC 6582) retransmits the next missing segment on each partial ACK and stays in fast recovery until the data outstanding at entry is acknowledged.
  • Why does RFC 5681 compute ssthresh from FlightSize rather than from cwnd?
    FlightSize is what is really in the network. `cwnd` can have grown past it, for example past `rwnd` or while the application sent less than allowed, so halving `cwnd` could leave `ssthresh` above the load that actually caused the loss.
  • Does CUBIC change fast retransmit or fast recovery?
    No. RFC 9438 says CUBIC makes no changes to those algorithms. It changes the size of the reduction, a factor of 0.7 instead of 0.5, and how the window grows afterwards.

saying these in an interview costs you the question

  • Three duplicate ACKs reset cwnd to one segment, exactly like a timeout.
  • After a timeout TCP resumes at half its old window in congestion avoidance.
  • A single duplicate ACK is enough to trigger fast retransmit.
  • Duplicate ACKs mean the network has stopped delivering data.
  • Fast recovery keeps the old window, so throughput is unaffected by the loss.