No one tells a TCP sender how much bandwidth is available — it has to discover it, and keep re-discovering it as conditions change, all while sharing the link fairly with everyone else. It does this by adjusting a congestion window (cwnd): how much unacknowledged data it will keep in flight. It starts cautious and grows the window exponentially to quickly find the ballpark (slow start), then switches to growing linearly to probe gently for more (congestion avoidance). The instant a packet is lost — the network’s way of saying “too much” — it halves the window and probes again. This additive-increase / multiplicative-decrease (AIMD) loop produces the iconic sawtooth that, remarkably, makes millions of independent senders converge to a fair, stable share. Grow the window, inject a loss, and watch it recover.
AIMD · slow start (exponential) → congestion avoidance (linear) → halve on loss · fair & stable
cwnd
The congestion window — how much unacknowledged data the sender keeps in flight.
slow start
Grow cwnd exponentially (double each RTT) until reaching the threshold.
congestion avoidance
Above the threshold, grow cwnd linearly (+1 per RTT) — gentle probing.
AIMD
Additive increase, multiplicative decrease: +1 per RTT, halve on loss.
tcp.js — additive up, multiplicative down
Ready
The congestion window starts small and grows each round-trip: exponentially in slow start, then linearly in congestion avoidance. Inject loss to halve it. Each RTT advances one round and plots the window. Build the sawtooth.
1
cwnd
16
ssthresh
slow start
phase
How it works
The genius of AIMD is that its two asymmetric rules produce fairness and stability from purely local decisions. Additive increase (nudge cwnd up by one segment per round-trip during congestion avoidance) makes senders probe for spare capacity slowly and gently, so they don’t all rush in and overwhelm the link. Multiplicative decrease (halve cwnd on a loss) backs off aggressively the moment the network signals overload, clearing congestion fast. When several flows share a bottleneck, the flow using more bandwidth loses more on each halving (it has more to lose), so repeated AIMD cycles pull the flows toward an equal share — this is why the internet doesn’t collapse under load even though no one coordinates it. Slow start is the launch phase: because a new connection has no idea of the capacity, doubling cwnd each RTT finds the right order of magnitude in just a handful of round-trips, then hands off to the gentle linear probing of congestion avoidance once it nears the last-known-good threshold (ssthresh).
1
Slow start: grow exponentially
A new connection starts with a tiny window and doubles it every round-trip, rapidly discovering the rough capacity of the path in just a few RTTs.
2
Congestion avoidance: grow linearly
Once the window reaches the threshold (ssthresh), the sender switches to adding just one segment per RTT — probing gently for any extra bandwidth without overshooting.
3
Loss → multiplicative decrease
A lost packet means the network is overloaded. The sender sets ssthresh to half the current window and cuts the window (to half, in modern TCP fast-recovery), then resumes probing.
✓
The sawtooth, and fairness
Repeating additive-increase then multiplicative-decrease traces a sawtooth. Across many flows sharing a link, the heavier user loses more per halving, so the flows converge to a fair, stable share — with no coordination.
Slow start
cwnd ×2 / RTT
Avoidance
cwnd +1 / RTT
On loss
cwnd ÷2
Result
fair sawtooth
The code
# TCP congestion control (Reno-style), per round-tripon ack: # a successful round-tripif cwnd < ssthresh:
cwnd *= 2 # slow start: exponentialelse:
cwnd += 1 # congestion avoidance: linearon packet_loss: # the network says "too much"
ssthresh = cwnd / 2
cwnd = ssthresh # multiplicative decrease (fast recovery)# (classic Tahoe drops cwnd to 1 and re-enters slow start)
Quick check
1. How does the congestion window grow during slow start?
Slow start doubles cwnd every RTT (despite the name), so a new connection finds the rough capacity of the path in just a few round-trips. It switches to linear growth once cwnd reaches the ssthresh threshold.
2. What does TCP do when it detects packet loss?
Loss signals congestion, so TCP backs off hard: it sets ssthresh to half the current window and reduces cwnd (to half in modern fast-recovery, to 1 in classic Tahoe), then resumes probing. Aggressive backoff clears congestion quickly.
3. Why does AIMD lead to fairness among competing flows?
Multiplicative decrease means the flow with the larger window loses more absolute bandwidth on each loss event, while additive increase treats everyone equally. Over many cycles this drives competing flows toward an equal, fair share — with no coordination.
FAQ
What is TCP congestion control?
How a TCP sender adjusts its rate to avoid overwhelming the network, discovering bandwidth without being told it. It maintains a congestion window (cwnd) limiting in-flight data, grows it when healthy (exponential slow start, then linear avoidance), and shrinks it on congestion (loss/delay). The classic scheme is AIMD.
What is the difference between flow control and congestion control?
Flow control protects the receiver (its advertised receive window caps in-flight data so a slow receiver isn’t overwhelmed). Congestion control protects the network (the sender infers a congestion window from loss/delay). The sender’s real limit is the minimum of the two windows.
What are the different TCP congestion control algorithms?
Tahoe/Reno are classic loss-based (Reno adds fast recovery). CUBIC (Linux default) grows the window as a cubic of time since last loss, scaling better on fast high-latency links. BBR (Google) is model-based — it estimates bottleneck bandwidth and RTT and paces to match. Others: Vegas (delay-based), DCTCP (data centers).
Why does loss mean congestion in TCP?
On wired links, packets are rarely lost to corruption — the usual cause is a router queue overflowing from too much traffic, so TCP treats loss as an implicit "too fast" signal and backs off. This breaks on lossy wireless (corruption loss isn’t congestion), which motivated delay/model-based schemes like BBR.