§4.4
Chapter 04 · Stream Multiplexing and Flow Control for Performance

§4.4Head of Line Blocking Avoidance with Stream-Level Reasoning

RFC 9000RFC 9114

Learning objective. Explain precisely why QUIC's streams remove the transport head-of-line blocking that afflicts HTTP/2 over TCP, what "per-stream" ordering still guarantees, and the one place blocking sneaks back in.

The same loss, two outcomes #

§1.1 introduced the problem; here is the mechanism. TCP exposes a single, strictly ordered byte stream. HTTP/2 multiplexes many logical streams into that one byte stream, so when a segment is lost, the receiver's TCP stack must hold back every later byte (including fully-arrived bytes for unrelated streams) until the gap is retransmitted. The multiplexing is real, but it rides on a transport that cannot deliver out of order.

QUIC gives each stream its own ordered byte space and its own reassembly. The receiver can hand stream B's bytes to the application while stream A still has a hole, because nothing about A's missing data constrains B's offsets ([RFC 9000 §2]). One lost packet delays only the stream(s) whose data it carried.

Receiver + appSenderTCP + HTTP/2 — one orderedbytestreamB is buffered butNOT delivered —the gap at A blockseverything behind itQUIC + HTTP/3 — independentstreamsB delivered to theapp immediately —only stream A waitsfor its retransmitsegment with stream A data(LOST)segment with stream B data(arrives)packet with stream A data (LOST)packet with stream B data (arrives)
Fig. 4.4-1One lost packet, two transports. Under TCP the gap at stream A stalls the already-arrived stream B; under QUIC, B is delivered immediately and only A waits for its retransmit.RFC 9000 §2; RFC 9114 §1

What "per-stream" actually promises #

The guarantee is precise, and the precision matters. QUIC removes head-of-line blocking between streams; it does not remove ordering within a stream. A byte lost early in stream A still blocks later bytes of stream A — that is the reliability and ordering a stream promises (§4.1). So the correct statement is: a loss is confined to the stream it hit. If you multiplex independent objects onto separate streams, a loss affecting one leaves the others untouched; if you serialize independent objects onto a single stream, you have rebuilt TCP's problem inside one QUIC stream.

This is the actionable design rule of the whole chapter: give independent things independent streams. An HTTP/3 connection does this automatically: one bidirectional stream per request (§1.3). That is exactly why a lost packet for one response does not stall the others.

Where blocking sneaks back #

"No head-of-line blocking" is true only when scoped carefully. Two residual couplings remain, both covered later:

  • QPACK header decompression. If a HEADERS block references dynamic-table entries that arrived on the (separate) QPACK encoder stream but have not yet been processed, the decoder must wait, reintroducing a cross-stream stall at the compression layer, not the transport (§6.3). QUIC bounds this with SETTINGS_QPACK_BLOCKED_STREAMS.
  • Shared connection flow control. A stream whose application has stopped reading holds buffered bytes against the connection-level MAX_DATA window, starving other streams of credit (§4.2): a flow-control stall rather than a delivery stall.

Neither is the transport-level, one-loss-stalls-all blocking of TCP; both are narrower and independently mitigated. Naming which layer a stall lives in is most of diagnosing it.

Note

Datagrams (§3.4) sidestep the question entirely: DATAGRAM frames are not ordered or retransmitted, so there is no head-of-line to block. That is the right tool when even per-stream ordering is more than the application wants — but it trades away reliability to get there.

In practice

editorial The migration from HTTP/2 to HTTP/3 quietly changes what "bundle your assets" means. Under HTTP/2, concatenating many small files into one big response reduced per-stream overhead and dodged some prioritization pitfalls; under HTTP/3 it can hurt, because a single loss in that big response now blocks all of it, where separate streams would have isolated the damage. On a lossy path, prefer more, smaller streams over fewer, larger ones: the opposite of the old bundling instinct (§4.5).

Takeaways #

QUIC confines a loss to the stream it hit by giving every stream its own ordered byte space; per-stream ordering still holds, so the rule is to map independent work onto independent streams. The only residual blocking lives above the transport (QPACK decompression and shared flow control), and each is separately bounded. §4.5 puts streams, flow control, priority, and this isolation together into a concrete stream plan for a mixed workload.