§3.3
Chapter 03 · Reliability, Loss Recovery, and Congestion Control Mechanics

§3.3Congestion Control Algorithms and Their QUIC Integration Points

RFC 9002

Learning objective. Describe QUIC's default congestion controller and its three states, and, more durably, the integration points through which any controller plugs in, so you can reason about CUBIC or BBR deployments even though the spec describes NewReno.

A controller, not the controller #

RFC 9002 specifies a NewReno-style congestion controller, but it is careful to frame it as a controller, not the only one: an endpoint "MAY unilaterally choose a different algorithm, such as CUBIC" ([RFC 9002 §7]). This is possible because QUIC cleanly separates loss recovery (which packets are lost, §3.1§3.2) from congestion control (how fast to send). The recovery machinery hands the controller a small, well-defined set of signals, and the controller decides the sending rate from them. Those signals are the real, lasting subject of this section:

  • bytes in flight and bytes newly acknowledged, from each ACK;
  • congestion events: a packet declared lost, an increase in the peer's reported ECN-CE count, or persistent congestion;
  • RTT samples (§3.1), which time-based controllers like BBR lean on;
  • the fact that the controller is per path, so migration resets it (§2.4).

Because these signals are algorithm-agnostic, swapping NewReno for CUBIC or BBR changes how the window responds, not what QUIC measures or how it detects loss.

The three states of NewReno #

The default controller moves between three states, starting from a congestion window of kInitialWindow (constants.md §8):

loss or ECN-CE

loss or ECN-CE

a packet sent during recovery is acked

persistent congestion (cwnd = min)

persistent congestion (cwnd = min)

Slow start — cwnd += bytes acked (exponential)

Recovery — ssthresh = cwnd/2, cwnd = max(ssthresh, min)

Congestion avoidance — +~1 datagram per RTT

Fig. 3.3-1NewReno's three states. Slow start grows the window exponentially; a congestion event drops into recovery (window halved); recovery gives way to additive-increase congestion avoidance; persistent congestion collapses the window and restarts slow start.RFC 9002 §7.3
  • Slow start. The window grows by the number of bytes acknowledged, doubling per round trip, until a congestion event or the slow-start threshold (ssthresh, initially infinite) is reached. This is the fast ramp at connection start.
  • Recovery. On a congestion event, the sender sets ssthresh = cwnd × kLossReductionFactor (0.5) and cwnd = max(ssthresh, kMinimumWindow), a multiplicative decrease. It stays in recovery, ignoring further congestion events, until a packet sent during recovery is acknowledged; this prevents one burst of loss from halving the window repeatedly ([RFC 9002 §7.3.2]).
  • Congestion avoidance. After recovery, the window grows by roughly one maximum datagram size per round trip: the cautious additive increase that probes for more bandwidth without overwhelming the path.

ECN-CE marks are treated as congestion events exactly like loss, but without a packet being dropped. The network signals congestion early and QUIC backs off without losing data ([RFC 9002 §7.1]).

When the path genuinely fails #

The exception to "never overreact" is persistent congestion (§3.2): if, once acknowledgments resume, the sender finds that a period spanning at least kPersistentCongestionThreshold (3) PTOs was entirely lost, it concludes the path collapsed and cuts the window to kMinimumWindow, restarting slow start. This is the one place a controller reacts drastically. Even here it is acting on confirmed evidence that everything in a window-plus-timers span was lost, not on a single silent timeout.

Note

Senders also pace their output (spreading a window's worth of packets across the RTT rather than bursting them) to avoid self-inflicted queue overflow and loss ([RFC 9002 §7.7]). Pacing is orthogonal to the state machine above: it shapes when the allowed bytes leave, while the controller sets how many are allowed.

In practice

editorial Most production QUIC stacks do not ship plain NewReno. Chromium and many servers default to CUBIC (a less aggressive-backoff, more scalable curve on high-BDP links), and BBR is widely deployed where it can be tuned. It models bottleneck bandwidth and RTT rather than treating loss as the only congestion signal, which helps on lossy wireless paths where NewReno over-reacts. The takeaway from the integration-points framing: you can read a capture's loss, ACK, and ECN signals the same way regardless of controller, and reason about the window response separately (Chapter 8).

Takeaways #

QUIC's congestion control is a swappable policy fed by a fixed set of signals — bytes acked, loss, ECN-CE, RTT — with NewReno's slow-start / recovery / congestion-avoidance cycle as the default, multiplicative decrease on congestion and additive increase otherwise, and a window collapse reserved for confirmed persistent congestion. §3.4 applies all of Chapter 3 to the hardest case: real-time traffic under loss and jitter.