TCP Congestion Control: Slow Start and AIMD
How TCP finds a safe rate: slow start doubles the window, congestion avoidance adds one per round trip, loss halves it. A simulation shows the sawtooth and two flows converging.
TCP congestion control sets how many unacknowledged segments a sender may have in flight. Slow start doubles the congestion window every round trip until ssthresh, congestion avoidance then adds one segment per round trip, and a loss halves the window. Interviewers expect why this AIMD rule is used: it makes competing flows converge to fair, efficient shares.
TCP starts with a small congestion window, doubles it every round trip (slow start), then grows it by one segment per round trip (congestion avoidance) and halves it on loss. This additive-increase, multiplicative-decrease rule produces a sawtooth and makes competing flows converge to equal shares, as a simulation of two flows showed.
- TCP slow start increases the congestion window by one segment per ACK, which doubles cwnd every round trip: 1, 2, 4, 8, 16, 32, 64.
- Above the slow start threshold ssthresh, TCP congestion avoidance grows cwnd by about one segment per round trip, and a loss halves it.
- Chiu and Jain showed in 1989 that additive increase, multiplicative decrease (AIMD) converges to a fair and efficient allocation among competing flows.
- In a simulation of two AIMD flows sharing a link of capacity 100, flows starting at 80 and 10 converged to nearly equal windows, each sawtoothing between about 25 and 50.
- In TCP a timeout is a severe signal that drops cwnd to 1 and restarts slow start, while three duplicate ACKs trigger fast retransmit and halve cwnd.
- A small download such as a 50 KB web page finishes while TCP is still in slow start, so its speed is limited by round-trip time rather than bandwidth.
01Flow control versus congestion control
Flow control stops a sender from overrunning the receiver, using the window size the receiver advertises. Congestion control is about the network between them. If every sender transmitted as fast as it could, routers' queues would overflow, packets would be dropped, retransmissions would add more load, and throughput would collapse. This did happen on the early internet in the 1980s, and TCP congestion control was added in response.
The sender keeps a congestion window (cwnd), the number of segments it may have in flight without acknowledgement. The actual limit is the smaller of cwnd and the receiver's window. The question is how to set cwnd when nobody tells you the network's capacity.
02Slow start
A new connection does not know the available bandwidth. It starts with a small cwnd (historically 1 segment, now commonly 10) and increases it by one segment for every ACK received. Since a full window of ACKs comes back each round trip, this doubles cwnd every round trip. Despite the name, growth is exponential: 1, 2, 4, 8, 16, 32, 64.
It continues until it hits a threshold (ssthresh) or a loss occurs. In my simulation, with a network that can hold 64 segments, cwnd reached 64 after 6 round trips.
03Congestion avoidance and AIMD
Once cwnd reaches ssthresh, TCP grows it by about one segment per round trip. This is additive increase. When a loss signals congestion, it cuts cwnd to half (multiplicative decrease) and sets ssthresh to that value. The combination is AIMD.
def reno(rtts=40, cap=64, ssthresh=64):
cw, hist, losses = 1, [], []
for r in range(rtts):
hist.append(cw)
if cw > cap: # network full: loss
ssthresh = max(cw // 2, 2)
cw = ssthresh
losses.append(r)
elif cw < ssthresh:
cw = min(cw * 2, ssthresh if ssthresh < cap else cw * 2)
else:
cw += 1
return hist, losses
hist, losses = reno()
print(hist[:12], losses) # [1, 2, 4, 8, 16, 32, 64, 65, 32, 33, 34, 35] [7]This is a simplified model of the Reno variant with one assumption: a loss occurs whenever cwnd exceeds the network capacity of 64. The output begins [1, 2, 4, 8, 16, 32, 64, 65, 32, 33, 34, 35, ...] with a loss at round trip 7. The window doubled up to 64, crept to 65 and overflowed, then dropped to 32 and began climbing one per round trip. Plotted over time this is the sawtooth shape that TCP traffic is known for. Real TCP has more detail, including fast retransmit, fast recovery, timeouts that reset cwnd to 1, and selective acknowledgements.
04Why additive increase and multiplicative decrease
Why not add and subtract, or multiply in both directions? Chiu and Jain showed in 1989 that AIMD converges to a fair and efficient allocation among competing flows. Increasing additively gives every flow the same absolute increase, which narrows the relative gap between a big and a small flow. Decreasing multiplicatively cuts the big flow by more in absolute terms.
I simulated two flows sharing a link of capacity 100, starting very unequal at 80 and 10:
a, b = 80.0, 10.0
for r in range(200):
if a + b > 100: a /= 2; b /= 2 # shared loss: both halve
else: a += 1; b += 1 # both add one
print(round(a), round(b)) # 36 36After 200 rounds both flows were at 36, but that number is only where the sawtooth happened to be at that round: each flow keeps oscillating between about 25 and 50, always nearly equal to the other, while the pair together oscillates up to the capacity of 100. Each loss halved the gap between them and each increase left it unchanged, so the gap shrank towards zero. That is fairness without any central coordination.
05Loss as the signal, and its limits
Reno-style TCP treats packet loss as the sign of congestion. On a wireless link where packets are lost for other reasons, this wrongly slows the sender. It also fills router buffers before reacting: with large buffers, queues grow, latency climbs (bufferbloat), and loss only occurs at the end.
Newer algorithms respond to other signals. CUBIC, the default on Linux for many years, grows the window with a cubic function of time since the last loss, which suits high-bandwidth long-distance links. BBR estimates the bottleneck bandwidth and round-trip time and paces sending to match, aiming to avoid filling queues. ECN lets routers mark packets instead of dropping them to signal congestion early.
06What this means in practice
A small download, such as a web page of 50 KB, finishes while TCP is still in slow start, so its speed is limited by round-trip time, not by link bandwidth. That is why latency matters more than bandwidth for typical web browsing, and why reusing connections (HTTP keep-alive and HTTP/2) helps: a warm connection has already grown its window. It is also why a larger initial window (10 segments) was standardised.
A long transfer on a fast, distant link can take many seconds to ramp up. Congestion control also runs per connection, so opening many parallel connections takes more than a fair share, which is one reason it is discouraged.
07Common interview questions
Q: What are the phases of TCP congestion control?
A: Slow start, congestion avoidance, and recovery after loss (fast retransmit and fast recovery).
Q: What does AIMD stand for and why use it?
A: Additive increase, multiplicative decrease. It converges to fair, efficient sharing among flows.
Q: What is ssthresh?
A: The slow start threshold. Below it cwnd doubles per RTT, above it grows linearly.
Q: What happens on a timeout versus three duplicate ACKs?
A: A timeout is a severe signal and cwnd drops to 1 with slow start again. Three duplicate ACKs trigger fast retransmit and cwnd is halved.
Q: Why is slow start called slow?
A: Because it starts from a small window, though growth is exponential.
Q: What is bufferbloat?
A: Excess latency caused by oversized router buffers that fill up before any loss signals congestion.