Introduction

Every time your browser fetches a page, your operating system is quietly negotiating how fast it can push data. Too fast and packets pile up, routers drop them, and the whole network chokes. Too slow and you waste capacity you paid for. The algorithm in the middle is TCP congestion control.

The classic answer, TCP Reno (early 1990s), uses additive increase, multiplicative decrease (AIMD): grow the congestion window WW by one segment per round-trip time (RTT), and halve it on loss. That linear ramp works fine on a dial-up modem, but on a modern 10 Gbps link with 50 ms of latency, you need tens of thousands of in-flight packets just to fill the pipe — and AIMD spends enormous time crawling back up after every loss.

TCP CUBIC (Sangtae Ha, Injong Rhee, and Lisong Xu, 2008; Linux default since kernel 2.6.19 in 2006) replaces that linear ramp with a cubic polynomial. After a loss event, the window grows as a function of elapsed time since the last loss, not of RTTs:

W(t)=C(tK)3+WmaxW(t) = C\,(t - K)^{3} + W_{\max}

where WmaxW_{\max} is the window at the last loss, CC is a scaling constant, KK is chosen so that W(0)=Wmax/2W(0) = W_{\max}/2 (the window right after loss), and tt is the time elapsed since that loss. The cubic shape means growth is fast far from WmaxW_{\max} and gentle near it — exactly where you want to probe carefully to avoid triggering another loss.

Crucially, the formula uses wall-clock time, not RTT count. A connection with a 5 ms RTT and one with a 200 ms RTT both grow at the same wall-clock rate — making CUBIC RTT-fair across very different paths.

CUBIC vs Reno

The simulation below runs both algorithms in parallel from the same loss event and shows how their congestion windows (WW) evolve over time. Press Simulate loss to trigger a loss event and watch them recover; press Reset to start fresh.

<!-- {{c_html_intro}} -->
<div class="controls">
  <label class="ctrl-label">{{lbl_rtt}} <input id="rtt" type="range" min="10" max="200" value="50" step="10"> <span id="rtt-val">50 ms</span></label>
  <label class="ctrl-label">{{lbl_bw}} <input id="bw" type="range" min="10" max="200" value="100" step="10"> <span id="bw-val">100 Mbps</span></label>
</div>
<canvas id="chart" width="560" height="260"></canvas>
<div class="legend">
  <span class="dot cubic"></span> CUBIC
  <span class="dot reno"></span> Reno
</div>
<div class="status" id="status">{{status_ready}}</div>
<div class="btns">
  <button id="btn-loss" type="button">{{btn_loss}}</button>
  <button id="btn-reset" type="button" class="ghost">{{btn_reset}}</button>
</div>
/* {{c_css_intro}} */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.controls { display: flex; flex-wrap: wrap; gap: .6rem 1.2rem; margin-bottom: .6rem; font-size: .85rem; }
.ctrl-label { display: flex; align-items: center; gap: .4rem; }
input[type=range] { width: 100px; accent-color: #1d3557; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 8px; background: #f8fafb; max-width: 100%; }
.legend { font-size: .82rem; margin: .4rem 0; display: flex; gap: 1rem; align-items: center; }
.dot { display: inline-block; width: 12px; height: 12px; border-radius: 50%; }
.dot.cubic { background: #1d6fa5; }
.dot.reno  { background: #e06c00; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin: .3rem 0; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
         background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
// Code not found

Notice that Reno climbs at a constant one-segment-per-RTT slope, while CUBIC rockets past and then flattens as it approaches the old maximum — probing carefully right where another loss is most likely. On high-bandwidth, high-latency paths the difference in throughput can be dramatic.

The Real Complexity

CUBIC's elegance hides several careful design decisions.

The cubic window function. The parameter KK is derived at each loss event so that the window recovers exactly to WmaxW_{\max} at time t=Kt = K:

K=WmaxβC3K = \sqrt[3]{\frac{W_{\max} \cdot \beta}{C}}

where β=0.3\beta = 0.3 is the multiplicative decrease factor and C=0.4C = 0.4 is the cubic scaling constant (Linux defaults). Far below WmaxW_{\max}, the cubic term is large and growth is steep. Near WmaxW_{\max}, the derivative 3C(tK)23C(t-K)^{2} is small — CUBIC slows down precisely where it needs to probe gently.

RTT-independence. Classic AIMD grows by one segment per RTT, so a 200 ms connection grows four times slower than a 50 ms one. CUBIC's time-based growth means every connection races up the same cubic curve at the same wall-clock speed. Long-distance flows are no longer penalized.

TCP-friendliness mode. When RTTs are short and the network is lightly loaded, pure cubic growth can be slower than Reno's linear ramp. CUBIC detects this case by comparing its window to the Reno estimate West(t)=Wmax/2+3β/(3β)t/RTTW_{\text{est}}(t) = W_{\max}/2 + 3\,\beta/(3-\beta) \cdot t/RTT and falls back to the faster one. This ensures CUBIC never under-uses a path that Reno could fill.

Bandwidth-delay product (BDP) awareness. The real bottleneck on a fat-and-long pipe is the bandwidth-delay product — the number of bits in flight at full speed. For a 10 Gbps link with 100 ms RTT, BDP 125\approx 125 MB, or roughly 90,000 segments. AIMD can take hundreds of seconds to climb there; CUBIC reaches it in seconds.

The algorithm is described in RFC 9438 (a playful cross-link; the actual RFC is at the footer) and studied in the same body of work as network flow algorithms that model how data moves through constrained pipes.

Where It Matters

TCP CUBIC is not an academic curiosity — it is the default congestion-control algorithm on every modern Linux kernel, which means it runs on:

  • Android phones: every TCP connection your phone makes uses CUBIC.
  • Cloud providers: AWS, Google Cloud, and Azure all run Linux host stacks with CUBIC (or derivatives like BBR that build on the same RTT-independence insight).
  • Content delivery: streaming video from Netflix, YouTube, and similar platforms relies on CUBIC to rapidly saturate high-bandwidth paths between CDN nodes.
  • Data centers: east-west traffic between servers shares the same algorithm, balancing thousands of concurrent flows.
  • Home broadband: your router's TCP stack (if it runs Linux — and most do) uses CUBIC for every upload.

CUBIC's successor in high-stakes deployments is often BBR (Bottleneck Bandwidth and RTT), which goes further by modeling the pipe directly rather than reacting to loss. But BBR is more complex and can be aggressive toward other flows; CUBIC remains the safer, well-understood default. Understanding CUBIC's cubic growth is also a clean entry point into broader ideas like scheduling and resource allocation under constraints.

Conclusion

TCP CUBIC's insight is deceptively simple: measure elapsed time after a loss, not elapsed round trips, and let the window grow as a cubic — fast at first, cautious near the danger zone. That single change freed long-distance connections from the RTT penalty that crippled AIMD on fat-and-long pipes.

The algorithm is solved and deployed: CUBIC has been the Linux default since 2006, standardized in RFC 9438, and its ideas — RTT-independence, careful probing near the previous maximum — have influenced every major congestion-control proposal since. The next time your download saturates a gigabit link, a cubic function is the quiet reason why.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/tcp-cubic/Content licensed under CC BY-NC 4.0.