The idea: three answers to "how fast may I send?"
A TCP sender does not know the speed of the slowest link on its path, the bottleneck. It has to find out by sending. Reno and CUBIC are loss-based: they keep sending more until the bottleneck's buffer overflows and a packet is lost, then back off. BBR is model-based: it measures the bottleneck bandwidth and the shortest round-trip time, and sends at exactly that rate. The page runs all three on identical copies of one link, on one clock, so you can compare their curves row by row.
The link, the queue and the BDP
Every row uses the same link: a bottleneck that sends 1 packet per ms (1500-byte packets, 12 Mbit/s) and a round-trip propagation delay RTprop = 100 ms. The pipe holds the bandwidth-delay product, BDP = 1 packet/ms × 100 ms = 100 packets. With fewer than 100 packets in flight the link is partly idle. With more, the extra packets wait in the bottleneck's buffer, and each one adds 1 ms to the RTT. When the buffer (20, 100 or 400 packets) is full, new packets are dropped.
On each chart the line is the packets in flight. The grey line is the BDP, the shaded band above it is the queue (read the delay on the right axis), the red line is “buffer full”, and ◆ marks a round with dropped packets.
Reno: additive increase, multiplicative decrease
Reno starts with slow start: cwnd begins at 10 packets and doubles every RTT (10, 20, 40, 80, 160, 320). At 320 the 200 places in pipe plus buffer overflow: 120 packets are dropped at 0.6 s. Reno halves cwnd (multiplicative decrease) and then adds one packet per RTT (additive increase). With a buffer of exactly one BDP, the halved window (100) still fills the link, so Reno uses 99 % of it, but it takes 15 s to climb from 100 back to 200 and loses again at 8.1 s and 23.5 s. That slow climb is Reno's problem on fast, long paths: at 10 Gbit/s and 100 ms one sawtooth would take over an hour. (Demo 1)
CUBIC: a cubic curve around the last loss
CUBIC (the default in Linux, Windows and macOS) remembers the window at the last loss, Wmax, cuts to 0.7 × Wmax, and then sets cwnd from the time since the loss, not from the number of ACKs:
W(t) = 0.4 × (t − K)³ + Wmax K = ∛(Wmax × (1 − 0.7) / 0.4)
The curve is concave first (it climbs fast back toward Wmax), nearly flat around t = K (it lingers where the last loss happened), then convex (if Wmax was not the limit, it probes faster and faster). With Wmax = 201 packets, K = 5.3 s. On the 1 BDP link CUBIC loses every 3 to 9 s instead of every 15 s, and because it sits on its plateau near the top, its average queue delay is higher than Reno's: 74 ms vs 56 ms. Two details show in the narration: fast convergence (after a loss below the previous Wmax, it remembers only 0.85 × the window, to leave room for newer flows) and the Reno-friendly estimate W_est, which takes over when the cubic curve would grow slower than Reno, on short RTTs or small windows.
BBR: measure the pipe, pace at its rate
BBR keeps two estimates: btlBw, the highest delivery rate seen in the last 10 rounds, and RTprop, the lowest RTT seen in the last 10 s. Their product is its BDP estimate. It sends at a pacing rate of gain × btlBw and caps the packets in flight at 2 × the BDP estimate. It moves through four states:
| State | Pacing gain | What it does | On the 1 BDP link |
|---|---|---|---|
| STARTUP | 2.89 | doubles the rate every round until btlBw stops growing by 25 % for 3 rounds | 0 – 0.7 s, fills the buffer, 217 packets dropped |
| DRAIN | 0.35 | empties the queue STARTUP built | 0.7 – 0.9 s |
| PROBE_BW | 1.25, 0.75, then 1 × 6 | one round probes for more bandwidth, the next drains what that probe queued | most of the run, queue 0 – 25 ms |
| PROBE_RTT | cwnd 4 packets | every 10 s without a lower RTT sample, empties the queue for 200 ms to re-measure RTprop | the dips at 10.2 s and 20.6 s |
The result is a nearly full link (97 %) with an average queue of 5 ms instead of 56 or 74 ms. BBR v1 ignores loss completely: the drops in STARTUP do not slow it down (Demo 2, Demo 6).
The buffer size decides who looks good
| After 30 s | Reno | CUBIC | BBR |
|---|---|---|---|
| 1 BDP buffer (Demo 1) | 99 %, 56 ms, 122 lost | 99 %, 74 ms, 151 lost | 97 %, 5 ms, 217 lost |
| 4 BDP buffer (Demo 3) | 99 %, 258 ms, 140 lost | 99 %, 322 ms, 160 lost | 97 %, 7 ms, 0 lost |
| 0.2 BDP buffer (Demo 4) | 88 %, 4 ms, 44 lost | 96 %, 7 ms, 47 lost | 96 %, 3 ms, 627 lost |
| 1 % random loss (Demo 5) | 14 %, 1 ms | 14 %, 0 ms | 96 %, 5 ms |
Cells are link used, average queue delay, packets lost.
- Deep buffer (bufferbloat). Loss-based senders fill whatever buffer there is: Reno and CUBIC add a quarter to a third of a second to every packet's RTT, which is what makes video calls lag while a download runs. BBR keeps its queue near zero.
- Shallow buffer. Reno's halved window (60) falls below the BDP, so the link idles after each loss: 88 %. CUBIC only cuts to 0.7 and loses less. BBR keeps the link full but its 1.25 probes overflow a 20-packet buffer every 8 rounds: it loses far more packets than the others and does not care.
- Random loss. A loss that has nothing to do with congestion (a radio link) still halves Reno's window. At 1 % the windows settle around 12–20 packets (the Mathis formula gives 1.22 / √0.01 ≈ 12), so Reno and CUBIC (here in its Reno-friendly region) use 14 % of the link. BBR does not react to loss and keeps 96 %.
When the flows share one link
The page gives every flow its own link so each curve is clean. On a shared link the differences turn into unfairness: CUBIC takes more than Reno on long paths, and BBR v1 can starve CUBIC in shallow buffers (it ignores the losses that make CUBIC back off) while losing to CUBIC in very deep ones. BBR v2 and v3 therefore treat heavy loss and ECN marks as a signal again and keep an upper bound on the packets in flight. See TCP Congestion Control for Reno's per-ACK rules, fast retransmit and timeouts, TCP Flow Control for the receiver's window, and Latency vs Throughput for why a full queue raises latency without raising throughput.
What the page leaves out
The model runs in whole rounds (one RTT each) with one packet size and no ACK details: real senders change cwnd per ACK, recover lost packets with SACK and fast recovery, and fall back to a timeout. CUBIC in Linux also uses HyStart, which ends slow start early when the RTT starts to rise, so its first overshoot is much smaller than here. BBR is version 1 with a fixed probing phase (real BBR picks a random one), and the delivery rate is a per-round average. Each flow has a link to itself, so there is no competition, no cross traffic and no AQM such as CoDel or FQ-CoDel, which would cap the queue delay for all three.