Intuition What this page is for
The parent note taught the machinery : slow start, congestion avoidance, the two loss reactions, and CUBIC. This page drills the machinery into your hands by walking every distinct situation the sender's state machine can land in — every phase, every loss signal, every degenerate corner — with numbers you can check, and with pictures that carry the reasoning.
Before we start, recall the three quantities we track (all in units of MSS , one Maximum Segment Size):
cwnd — the congestion window, the sender's private guess of how much the network can take.
ssthresh — the "slow-start threshold": the cwnd value at which we stop doubling and start tiptoeing.
The transition rule: cwnd < ssthresh ⇒ slow start (double per RTT); cwnd ≥ ssthresh ⇒ congestion avoidance (add 1 per RTT).
The figure below is the map for the whole page: it shows cwnd (vertical) against RTTs (horizontal) for a Reno flow — the exponential slow-start ramp, the switch to the linear congestion-avoidance climb, the sharp halving on a dup-ACK, and the crash-to-1 on a timeout. Every one of the first seven examples is a zoom into one part of this same picture.
Figure: the master Reno sawtooth. Cyan = slow start (exponential), white = congestion avoidance (linear +1/RTT), amber dashed = ssthresh at each moment. Dup-ACK halves cwnd; timeout drops it to 1.
Every situation TCP congestion control can throw at you is one row below. The examples that follow are each tagged with the cell they cover, and together they fill the whole table.
Cell
Situation
Governing rule
A
Pure slow start, no loss, ends at ssthresh
double per RTT until cwnd ≥ ssthresh
B
Slow start → congestion avoidance handover mid-run
switch phase when threshold crossed
C
Loss = 3 duplicate ACKs (mild)
ssthresh ← cwnd/2; cwnd ← ssthresh; stay in CA
D
Loss = timeout (severe)
ssthresh ← cwnd/2; cwnd ← 1; restart slow start
E
Degenerate : cwnd already = 1, timeout again
floor at 1; ssthresh floored at 2
F
Zero / boundary : cwnd exactly equals ssthresh at start
which phase? (CA, by ≥ rule)
G
Real-world word problem : throughput from cwnd & RTT
throughput ≈ cwnd·MSS / RTT
H
CUBIC climb: compute K , window at several times
W ( t ) = C ( t − K ) 3 + W m a x
I
Limiting behaviour : CUBIC vs Reno on a long fat pipe
time-based vs RTT-based growth
J
Exam twist : full trace mixing SS, CA, dup-ACK and timeout
apply all rules in sequence
Prerequisite ideas we lean on: AIMD Fairness (why halving), Bandwidth Delay Product (why CUBIC exists), Queueing and Router Buffers (what "loss" physically means), and Flow Control vs Congestion Control (why cwnd ≠ rwnd).
MSS = 1 segment. Start: cwnd = 1, ssthresh = 16, no loss occurs. How many RTTs to reach ssthresh, and what is cwnd at each RTT?
Forecast: guess before reading — will it take about 4 RTTs, or about 16?
Step 1 — Write the doubling law. In slow start we add 1 MSS per ACK . With cwnd = W we send W segments per RTT, get W ACKs, so cwnd goes W → 2 W each RTT.
Why this step? This is the whole engine of slow start — every later phase is a modification of it, so we anchor it first.
Step 2 — Iterate. RTT0: 1 → RTT1: 2 → RTT2: 4 → RTT3: 8 → RTT4: 16. At RTT4 cwnd = ssthresh = 16, so cwnd < ssthresh is now false ⇒ leave slow start.
Why this step? We stop doubling at the threshold to avoid overshooting the network's real capacity (doubling once more would jump to 32 — a violent overshoot).
Step 3 — Answer. It takes 4 RTTs to climb from 1 to 16. The exponential 2 n makes it feel almost instant — that is why "slow start" is a misnomer.
Verify: 2 4 = 16 = ssthresh ✓. Notice 16 ≪ the naive guess of "16 RTTs" (that would be linear growth). Units: everything in MSS, RTTs counted as integers.
MSS = 1. Start cwnd = 1, ssthresh = 6. Trace cwnd for the first 6 RTTs. Note where doubling stops and +1 begins, counting individual ACKs during the crossing RTT.
Forecast: ssthresh = 6 is not a power of two. Guess: does cwnd land exactly on 6, or skip past it?
Step 1 — Double while below threshold. RTT0: 1 → RTT1: 2 → RTT2: 4. At the start of the next RTT cwnd = 4, and 4 < 6, so we are still in slow start.
Why this step? The phase is re-checked before each RTT; 4 < 6 keeps us doubling — for now.
Step 2 — Count the ACKs across the crossing RTT (the precise logic). With cwnd = 4 we send 4 segments, so 4 ACKs come back. In slow start each ACK adds 1 MSS, but only while cwnd < ssthresh ; the instant cwnd reaches ssthresh the per-ACK rule switches to the congestion-avoidance increment cwnd MSS 2 . Step through the 4 ACKs one at a time (see figure):
ACK 1: cwnd 4 → 5 (5 < 6, still slow start, add 1)
ACK 2: cwnd 5 → 6 (6 = ssthresh reached — this was the last slow-start add)
ACK 3: cwnd 6 → 6 + 6 1 ≈ 6.17 (now cwnd ≥ ssthresh ⇒ CA increment cwnd 1 )
ACK 4: cwnd ≈ 6.17 → 6.33 (CA increment again)
So the exact window at the end of RTT3 is 6 + 6 2 = 6. 3 MSS. Because only 2 of the 4 ACKs were slow-start adds, cwnd lands on 6 and then creeps, rather than jumping to 8. Textbook traces round this down and simply report cwnd = 6 at the end of RTT3.
Why this step? This is the ACK-level detail glossed over everywhere: the phase switch happens mid-RTT , on the exact ACK that first makes cwnd ≥ ssthresh, and the remaining ACKs of that RTT already use the CA rule. The figure plots these four ACK ticks so you can see the slope change between ACKs.
Step 3 — Linear from here. Rounding to cwnd = 6, subsequent RTTs add +1: RTT4: 7 → RTT5: 8 → RTT6: 9.
Why this step? Near capacity, additive increase (the AI of AIMD ) probes gently instead of doubling into a loss.
Figure: the crossing RTT drawn ACK-by-ACK. The first two cyan ACK-ticks add a full +1 (slow start); once the amber ssthresh=6 line is touched the last two white ACK-ticks add only cwnd 1 (congestion avoidance). The slope visibly flattens the moment we cross the amber line.
Verify: slow-start adds used = 2 (ACKs 1,2); exact end-of-RTT3 window = 6 + 6 2 = 6. 3 ; rounded trace 1, 2, 4, 6, 7, 8, 9. From RTT3 to RTT6 is 3 steps of +1: 6 + 3 = 9 ✓.
Sender is in congestion avoidance, cwnd = 20, ssthresh = 24. It receives 3 duplicate ACKs . New ssthresh, new cwnd, and which phase?
Forecast: dup-ACKs mean data is still flowing. Will cwnd drop to 1, or just halve?
Step 1 — Read the signal's severity. 3 dup-ACKs = one segment lost but later segments are arriving (that's what makes the ACKs duplicate). The pipe is not empty — a mild signal.
Why this step? The reaction must match the signal; misreading this is the classic mistake.
Step 2 — Multiplicative decrease. ssthresh ← cwnd/2 = 20/2 = 10 . Then cwnd ← ssthresh = 10 (this is Fast Recovery / Reno's halving — the MD of AIMD).
Why this step? Halving is a strong-but-not-catastrophic back-off that, paired with additive increase, yields the stable fair sawtooth. Note ssthresh = 24 before is irrelevant — the new ssthresh is based on current cwnd , not the old ssthresh.
Step 3 — Resume phase. Because we did not drop to 1, we continue in congestion avoidance (+1 per RTT) from cwnd = 10.
Why this step? Restarting slow start here would waste the knowledge that the network was recently near cwnd ≈ 20.
Verify: 20/2 = 10 ✓. cwnd = ssthresh = 10 ✓, phase = CA ✓. Sanity: after loss cwnd (10) < old cwnd (20), so we backed off — correct direction.
Same start as Example 3 — congestion avoidance, cwnd = 20, ssthresh = 24 — but this time a timeout fires (no ACKs at all). New ssthresh, new cwnd, phase, and RTTs to climb back to the new ssthresh.
Forecast: compared with the dup-ACK case, will the recovery be faster or slower?
Step 1 — Read severity. Timeout = no ACKs → the pipe may be truly empty / badly congested. This is the severe signal.
Why this step? Different signal ⇒ different, harsher reaction.
Step 2 — Reset. ssthresh ← cwnd/2 = 20/2 = 10 (same halving as before). But now cwnd ← 1 MSS, and we go back to slow start .
Why this step? With the pipe possibly empty we cannot trust any large window, so we rediscover capacity from scratch — but we remember the halved ssthresh so slow start knows where to stop.
Step 3 — Climb back. From cwnd = 1, doubling: 1 → 2 → 4 → 8 → (would be 16, capped at ssthresh 10). Doublings 1→2→4→8 are 3 RTTs; the 4th RTT crosses into ssthresh = 10 and switches to CA. So 4 RTTs to reach ssthresh = 10.
Why this step? Shows the real cost of a timeout: cwnd = 20 collapsed to 1 and needs several RTTs to recover — far worse than the dup-ACK case which stayed at 10 immediately.
Verify: 20/2 = 10 ✓, cwnd = 1 ✓. Doubling reaches 8 in 3 RTTs (2 3 = 8 ), then the next RTT lands on ssthresh = 10 (capped) ✓ → 4 RTTs total. Compare Cell C: dup-ACK left cwnd = 10 instantly; timeout needed 4 RTTs to regain 10 — the harsher penalty is confirmed.
cwnd = 2, ssthresh = 4. A timeout occurs. Then, before any real recovery, another timeout occurs while cwnd = 1. What are ssthresh and cwnd after each?
Forecast: can ssthresh be halved forever down toward 0? Can cwnd go below 1?
Step 1 — First timeout. ssthresh ← cwnd/2 = 2/2 = 1 — but a threshold of 1 would leave no room for slow start at all, so implementations enforce a floor of 2 MSS: ssthresh ← max(cwnd/2, 2) = 2 . cwnd ← 1, restart slow start.
Why this step? This is the degenerate case the phase rules must survive; without the floor, ssthresh could collapse to 0 or 1 and slow start would have nowhere to grow.
Step 2 — Second timeout (cwnd = 1). ssthresh ← max(1/2, 2) = 2 again. cwnd ← 1 (cannot go below 1 — you must always send at least one segment to make progress).
Why this step? Shows the hard floors: cwnd bottoms out at 1, ssthresh at 2. The system degrades gracefully instead of dividing to zero.
Verify: max(2/2, 2) = max(1,2) = 2 ✓; max(1/2, 2) = 2 ✓; cwnd never < 1 ✓. Repeated timeouts pin the sender at the (cwnd=1, ssthresh=2) floor — the slowest survivable state, exactly as designed.
Fresh connection tweak: cwnd = 8, ssthresh = 8 at the start (no loss). Which phase, and what is cwnd after 2 RTTs?
Forecast: equal values — does it double (slow start) or add 1 (CA)?
Step 1 — Apply the transition rule literally. The rule is cwnd < ssthresh ⇒ slow start, otherwise congestion avoidance. Here cwnd = 8 is not less than ssthresh = 8, so we are already in congestion avoidance .
Why this step? The boundary is decided by strict < vs ≥; getting the inequality direction right is the entire point of this cell.
Step 2 — Additive increase. RTT1: 8 + 1 = 9. RTT2: 9 + 1 = 10.
Why this step? CA adds exactly 1 MSS per RTT, so two RTTs give +2.
Verify: 8 is not < 8 ⇒ CA ✓. 8 + 2 = 10 ✓. If the rule had been ≤ we'd wrongly double to 16 — the ≥ convention prevents that overshoot at the boundary.
A link has RTT = 100 ms. MSS = 1500 bytes. TCP is holding a steady cwnd of 50 segments. Estimate the throughput in megabits per second (1 Mbit = 1 0 6 bits).
Forecast: cwnd·MSS is 75 000 bytes per RTT. Guess whether the answer is closer to 1 Mbps or 6 Mbps.
Step 1 — Bytes in flight per RTT. In one RTT the sender can have cwnd·MSS bytes outstanding: 50 × 1500 = 75000 bytes.
Why this step? Throughput ≈ (bytes we can push before needing an ACK) ÷ (time to get that ACK) = cwnd·MSS / RTT. This ties directly to Bandwidth Delay Product .
Step 2 — Divide by RTT. 75000 bytes /0.1 s = 750000 bytes/s.
Why this step? One full window drains and refills every RTT, so per-second rate is the window divided by RTT.
Step 3 — Convert to Mbps. 750000 bytes/s × 8 = 6 000 000 bits/s = 6 Mbps.
Why this step? Networking rates are quoted in bits; 1 byte = 8 bits.
Verify: 0.1 50 × 1500 × 8 = 6 × 1 0 6 bit/s = 6 Mbps ✓. Units: bytes·(bits/byte)/seconds = bits/second ✓.
Intuition What each CUBIC knob DOES to the curve (before any algebra)
CUBIC grows its window along a cube curve. Instead of memorising symbols, feel what each one does to the shape — the figure at the end of this example plots all of it:
W m a x (default: whatever cwnd was at the last loss) is the remembered ceiling — the amber line the curve flattens toward. Raise it and the whole S-shape slides up.
β (default 0.3 ) is how far we fall on loss: cwnd is multiplied by ( 1 − β ) , so a bigger β starts the curve lower and gives it a longer climb back.
C (default 0.4 ) is the steepness of the cube: bigger C ⇒ the curve rushes toward the ceiling sooner and blasts away faster afterward (more aggressive probing).
K is not a free knob — it is derived so the curve passes through the post-loss window at t = 0 and reaches W m a x at t = K . It is literally the time to climb back to the old peak .
Definition The CUBIC symbols, with units
W ( t ) = C ( t − K ) 3 + W m a x , where
t — wall-clock seconds since the last congestion event (NOT RTTs — this is CUBIC's key trick).
W m a x — cwnd (MSS) just before the last loss.
C — cubic scaling constant (units MSS/s³ so C ( t − K ) 3 comes out in MSS), default 0.4 .
β — multiplicative-decrease fraction (dimensionless), default 0.3 .
K = 3 W m a x β / C — seconds to climb back to W m a x ; the plateau/inflection of the cube.
CUBIC with W m a x = 200 MSS, C = 0.4 , β = 0.3 . Find K , the window right after loss, and W ( t ) at t = 0 , t = K , t = 2 K .
Forecast: at t = 2 K , will the window be far above the old peak of 200, or barely above it?
Step 1 — Post-loss window. CUBIC multiplies cwnd by ( 1 − β ) on loss: ( 1 − 0.3 ) × 200 = 140 MSS. This is where the cubic curve must start.
Why this step? K is defined so the curve passes through this exact point at t = 0 ; we need this value to check the curve later.
Step 2 — Compute K. K = 3 C W m a x β = 3 0.4 200 × 0.3 = 3 150 ≈ 5.313 s.
Why this step? K is the time to climb from the post-loss window back to the old peak W m a x — the plateau of the cube. This is wall-clock seconds , not RTTs (the key difference from Reno).
Step 3 — Evaluate the curve. W ( t ) = 0.4 ( t − K ) 3 + 200 .
t = 0 : 0.4 ( − 5.313 ) 3 + 200 = 0.4 ( − 150 ) + 200 = − 60 + 200 = 140 MSS ✓ (matches step 1).
t = K ≈ 5.313 : 0.4 ( 0 ) 3 + 200 = 200 MSS — plateau at the old ceiling.
t = 2 K ≈ 10.626 : 0.4 ( 5.313 ) 3 + 200 = 0.4 ( 150 ) + 200 = 260 MSS.
Why this step? The three points reveal CUBIC's shape: concave rush toward 200 (cautious near the danger zone), plateau at 200 (gently tests the ceiling), then convex blast to 260 (explore for new bandwidth).
The figure below plots exactly this curve. Read it left to right: the cyan curve is W ( t ) ; the three white dots are the points we just computed at t = 0 , K , 2 K ; the amber dashed line marks the remembered ceiling W m a x = 200 . Notice how the cyan curve flattens as it nears the amber line (concave, cautious) and steepens after the plateau (convex, exploring) — the whole intelligence of CUBIC is visible in this single S-shape.
Figure: CUBIC window W ( t ) = 0.4 ( t − K ) 3 + 200 . Below t = K the growth is concave (slows as it approaches the old peak W m a x ); at t = K it plateaus at W m a x = 200 ; above t = K it turns convex and accelerates upward to probe for new bandwidth. The three white dots are the checked values 140, 200, 260 MSS.
Verify: 3 150 ≈ 5.313 ✓; W ( 0 ) = 140 ✓; W ( K ) = 200 ✓; W ( 2 K ) = 260 ✓. Symmetry check: the cube is antisymmetric about t = K , so W ( 2 K ) − W m a x = W m a x − W ( 0 ) = 60 ✓.
A "long fat network" has RTT = 200 ms. After a loss both flows sit at cwnd = 140 MSS (old peak W m a x = 200 ). Using CUBIC parameters from Example 8, compare how each refills to 200 MSS. Reno adds 1 MSS per RTT.
Forecast: Reno's +1/RTT with a 200 ms RTT — roughly how many seconds to gain the 60 MSS back?
Step 1 — Reno's refill time. Reno needs 200 − 140 = 60 more MSS at +1 per RTT ⇒ 60 RTTs. At 200 ms each: 60 × 0.2 = 12 s.
Why this step? Reno's growth is bolted to RTT; on a long-RTT pipe those 60 additive steps stretch into many seconds — this is the pain CUBIC was built to cure, and it ties to BDP .
Step 2 — CUBIC's refill time. CUBIC reaches W m a x = 200 at t = K = 3 150 ≈ 5.313 s — independent of RTT .
Why this step? CUBIC's W ( t ) is a function of wall-clock time, so its refill time does not blow up when RTT is large; we simply read off t = K from Example 8.
Step 3 — Compare. CUBIC: ≈ 5.31 s. Reno: 12 s. CUBIC refills the fat pipe more than 2× faster here, and — because K ignores RTT — two flows with different RTTs climb at similar wall-clock rates (RTT fairness). See BBR Congestion Control for a further departure from loss-based control.
Why this step? This is the whole reason CUBIC replaced Reno as the Linux default: time-based growth wins on long fat networks and treats different-RTT flows more fairly.
Figure: both flows start at 140 MSS after loss. The cyan CUBIC curve reaches the amber ceiling 200 at t≈5.31 s; the white Reno line (straight, +1 per 0.2 s RTT) only reaches 200 at t=12 s. The gap between the lines is the throughput CUBIC gains on a long fat pipe.
Verify: Reno 60 × 0.2 = 12 s ✓; CUBIC K = 3 150 ≈ 5.313 s ✓; ratio 12/5.313 ≈ 2.26 > 2 ✓.
MSS = 1. Start cwnd = 1, ssthresh = 8. Events per RTT:
RTT0 start; grow normally; at the end of RTT4 a triple dup ACK occurs; then grow; at the end of RTT7 a timeout occurs. Give cwnd at the start of RTT0…RTT9 and ssthresh after each loss.
Forecast: after the timeout, does cwnd go to the halved value or to 1?
Step 1 — Slow start to ssthresh. RTT0:1 → RTT1:2 → RTT2:4 → RTT3:8. At 8 = ssthresh ⇒ switch to CA.
Why this step? cwnd ≥ ssthresh triggers CA (the boundary rule proven in Cell F).
Step 2 — CA until the dup-ACK. RTT4: 8 + 1 = 9. End of RTT4: 3 dup ACKs at cwnd = 9 → ssthresh ← ⌊9/2⌋ = 4; cwnd ← ssthresh = 4 ; stay in CA (Cell C).
Why this step? Mild signal ⇒ halve and continue in CA, not restart — matching the reaction to the signal.
Step 3 — CA climb again. RTT5: 4 + 1 = 5. RTT6: 6. RTT7: 7. End of RTT7: timeout at cwnd = 7 → ssthresh ← ⌊7/2⌋ = 3 (and 3 ≥ 2, so no floor bump); cwnd ← 1 ; restart slow start (Cell D).
Why this step? Severe signal ⇒ cwnd to 1 and back to slow start, remembering the halved ssthresh so we know where to stop.
Step 4 — Slow start after timeout. RTT8: 1 → RTT9: 2 (doubling, since 1 < 3 and 2 < 3).
Why this step? We rediscover capacity from 1, doubling until we reach the new ssthresh = 3.
Figure: the full trace. Cyan = slow start, white = congestion avoidance, amber dashed = ssthresh (8, then 4 after the dup-ACK, then 3 after the timeout). The dup-ACK at end of RTT4 halves cwnd 9→4; the timeout at end of RTT7 crashes cwnd 7→1 and restarts the exponential ramp.
Verify: cwnd sequence at RTT0…RTT9 = 1, 2, 4, 8, 9, 4, 5, 6, 7, 1, 2 (start-of-RTT values; the dup-ACK at end of RTT4 sets RTT5's start to 4, the timeout at end of RTT7 sets RTT8's start to 1). ssthresh: 8 → 4 (after dup-ACK) → 3 (after timeout). Checks: ⌊ 9/2 ⌋ = 4 ✓, ⌊ 7/2 ⌋ = 3 ✓, timeout drives cwnd to 1 ✓.
Recall Self-test — cover the answers
Mild loss (3 dup ACKs): cwnd becomes what, phase what? ::: cwnd ← ssthresh (≈ half), stay in congestion avoidance.
Severe loss (timeout): cwnd becomes what, phase what? ::: cwnd ← 1 MSS, restart slow start; ssthresh halved.
At the boundary cwnd = ssthresh, which phase? ::: Congestion avoidance (rule is cwnd < ssthresh ⇒ slow start).
CUBIC's W ( t ) depends on RTT or wall-clock time? ::: Wall-clock time since the last loss — that gives RTT fairness.
The formula for K ? ::: K = 3 W m a x β / C — the time to climb back to the old peak.
Mnemonic Match reaction to signal
"Dup = halve, Time = ONE." Three dup ACKs → halve (mild). Time out → back to 1 (severe).