In TCP, how do slow start and congestion avoidance each grow the congestion window, and how does ssthresh decide which one runs?
answer
- fast growth, then careful growth
- per ACK versus per round trip
- cwnd compared with ssthresh
- ssthresh starts arbitrarily high
- initial window set by SMSS
basics
~20 sIn slow start a TCP sender adds up to one SMSS per ACK of new data, roughly doubling cwnd each round trip; in congestion avoidance it adds about one SMSS per round trip. Slow start runs while cwnd is below ssthresh.
solid answer
~40 sRFC 5681 gives the sender two variables: `cwnd` and the slow start threshold `ssthresh`. While `cwnd < ssthresh` the sender is in **slow start**: each ACK for new data adds `min(N, SMSS)` bytes, so with an ACK per segment the window roughly doubles every round trip. Above `ssthresh` it is in **congestion avoidance** and adds about one full-sized segment per round trip, never more. `ssthresh` starts arbitrarily high, so the first slow start usually ends at the first loss; after loss it becomes `max(FlightSize/2, 2*SMSS)`. The initial window is small: at most 2, 3 or 4 segments depending on SMSS (RFC 6928's 10 segments is Experimental). The result is the familiar AIMD sawtooth: add one segment per round trip, halve on loss.
code
pseudocode · 6 lines# RFC 5681 window growth, run on each ACK that acknowledges N new bytes
if cwnd < ssthresh: # slow start
cwnd = cwnd + min(N, SMSS)
else: # congestion avoidance
increment = (SMSS * SMSS) / cwnd # integer division
cwnd = cwnd + max(1, increment) # about 1 SMSS per round tripgo deeper
Recall the two phases by their growth shape: fast growth per ACK in slow start, one segment per round trip in congestion avoidance.
Explain the cwnd-versus-ssthresh switch, where ssthresh comes from at start and after loss, and why the initial window depends on SMSS.
Reason about how many round trips a short transfer spends in slow start, and why the initial window matters more to small responses than the steady-state algorithm.
Weigh a larger initial window against burst risk on shared bottlenecks, and why RFC 6928's 10 segments stayed Experimental rather than replacing RFC 5681's bound.
## The two variables RFC 5681 describes TCP congestion control with two sender-side state variables: - **`cwnd`**, the congestion window: how much unacknowledged data the sender allows itself to have in the network. RFC 5681 counts it in **bytes**. - **`ssthresh`**, the slow start threshold: the size at which the sender stops growing fast and starts growing carefully. The rule that picks the algorithm is simple: **slow start** runs when `cwnd < ssthresh`, **congestion avoidance** when `cwnd > ssthresh`, and at equality the sender may use either. ## The initial window A new connection knows nothing about the path, so RFC 5681 bounds the **initial window** (IW) by the sender's maximum segment size (SMSS): | SMSS | IW upper bound | |---|---| | above 2190 bytes | 2 segments | | above 1095 and up to 2190 bytes | 3 segments | | 1095 bytes or less | 4 segments | With a typical SMSS of 1460 bytes the bound is 3 segments, 4380 bytes. The SYN-ACK and its acknowledgment do not grow `cwnd`, and if the SYN or SYN-ACK was lost the initial window is one segment. RFC 6928 raises IW to 10 segments, but it is an **Experimental** RFC, not the standard rule. ## Slow start For every ACK that acknowledges new data, RFC 5681 recommends: - `cwnd += min(N, SMSS)`, where `N` is the number of newly acknowledged bytes. Counting acknowledged bytes rather than ACKs (**Appropriate Byte Counting**) stops a misbehaving receiver from inflating `cwnd` by splitting one segment's acknowledgment into many small ACKs. If every segment is acknowledged, each segment in flight produces one ACK and adds one segment to `cwnd`, so the window **roughly doubles every round trip**. Delayed ACKs, which acknowledge every second segment, make the growth slower. "Slow" refers to the starting point, a small window grown by the returning ACK clock, rather than to the growth rate. A trace with SMSS 1460 bytes, IW 3 segments and `ssthresh` at 32 segments, assuming every segment is acknowledged and nothing is lost: 1. Round trip 1: `cwnd` = 3 segments. 2. Round trip 2: 6 segments. 3. Round trip 3: 12 segments. 4. Round trip 4: 24 segments. 5. Round trip 5: growth continues per ACK until `cwnd` reaches 32 segments, then congestion avoidance takes over. 6. Round trips after that: 33, 34, 35 segments, one more each round trip. ## Congestion avoidance Above `ssthresh` the sender increases `cwnd` by roughly **one full-sized segment per round trip**. RFC 5681 says it MAY add SMSS bytes, SHOULD apply the byte-counting rule once per round trip, and MUST NOT add more than SMSS bytes per round trip. A common approximation, applied on every ACK of new data, is `cwnd += SMSS*SMSS/cwnd`, rounded up to 1 byte if integer arithmetic yields zero. ## Where ssthresh comes from - **At the start** RFC 5681 says `ssthresh` SHOULD be set arbitrarily high, for example to the largest possible advertised window, so that the network rather than a host guess decides when slow start ends. In practice the first slow start usually ends at the first loss. - **After loss** the sender sets `ssthresh = max(FlightSize / 2, 2*SMSS)`, where FlightSize is the data sent but not yet acknowledged. That halved value is the new "known safe" point. - **After a retransmission timeout** `cwnd` falls to one segment and slow start runs again up to the new `ssthresh`; slow start is therefore not only a connection-start event. ## The AIMD sawtooth Together these rules produce **additive increase, multiplicative decrease** (AIMD): add one segment per round trip, cut to about half on loss. Plotted over time, `cwnd` climbs in a straight line, drops sharply at each loss, and climbs again, a sawtooth. AIMD is what lets many flows sharing a bottleneck converge toward a fair share. CUBIC (RFC 9438) keeps the same slow start and loss detection but replaces the linear climb with a cubic curve and cuts to 0.7 rather than 0.5. ## Units to keep straight - `cwnd`, `ssthresh` and FlightSize are bytes in RFC 5681; RFC 9438 writes CUBIC's windows in segments. - The initial window is stated in segments with a byte bound. - "One segment per round trip" is a rate of growth, not a sending rate.
- Why does RFC 5681 recommend counting acknowledged bytes in slow start instead of adding SMSS per ACK?Because a receiver can send several ACKs for one segment, each covering part of it (ACK Division). Adding a full SMSS per ACK would let it inflate `cwnd` artificially. With `cwnd += min(N, SMSS)` the growth tracks data actually delivered, however the ACKs are split.
- Why does TCP not simply stay in slow start until the first loss every time?Exponential growth overshoots: in the round trip where the path fills, the window can grow to as much as twice what the path holds, so many segments are lost at once. After a loss, `ssthresh` remembers roughly half the window that caused it, so the sender grows fast only up to that point and probes linearly beyond it.
saying these in an interview costs you the question
- Slow start increases cwnd by one segment per round trip.
- Congestion avoidance doubles the congestion window every round trip.
- ssthresh is a fixed constant configured once for the whole connection.
- The TCP standard fixes the initial window at 10 segments.
- Slow start happens only once, at the very start of a connection.