Systems · Networking

Probe and Back Off.

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-trip on ack: # a successful round-trip if cwnd < ssthresh: cwnd *= 2 # slow start: exponential else: cwnd += 1 # congestion avoidance: linear on 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?

2. What does TCP do when it detects packet loss?

3. Why does AIMD lead to fairness among competing flows?

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.

Keep going

Finished this one? 0 / 44 Labs done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More Labs