A receiver holding bytes – with – missing delivers nothing — that’s ordering working correctly. But if those bytes belong to two unrelated things (a stylesheet and an image multiplexed over one connection), the image sits complete and unusable, waiting on a stylesheet packet it has nothing to do with.

Head-of-line blocking is not a bug. It’s the unavoidable price of a single connection-wide byte order.

HTTP’s three attempts to escape it:

  • HTTP/1.1 — one request at a time; pipelining required in-order responses, so a slow response blocked the rest. Browsers opened ~6 parallel connections instead.
  • HTTP/2 — generalises framing into typed, length-prefixed frames tagged with a stream ID, so streams interleave over one connection (HPACK compresses headers). Fixes application-level blocking — but all streams still ride one TCP byte stream, so one TCP-level loss stalls everything. The blocking moved down a layer.
  • HTTP/3 — the only remaining move: leave TCP.

The real question is “how do I avoid a shared order while keeping one connection?”, and it has exactly one answer: give each stream its own sequence space — which is impossible in TCP, because TCP’s sequence space is a single flat byte stream (the decision that bought packaging freedom). No option can subdivide it.

That impossibility is the whole story of QUIC (RFC 9000): a full transport in user space over UDP, giving per-stream ordering. Three further wins: handshake collapse (transport + TLS 1.3 in one exchange, 1 RTT or 0 on resumption), connection migration (an explicit Connection ID survives WiFi→cellular, where a TCP 4-tuple would not), and escaping ossification (encrypting nearly the whole header so middleboxes can’t inspect or “fix” it).

See also

References

Questions

flashcards/software-engineering/networking

What is head-of-line blocking and why isn’t it a bug?::A complete message stalls waiting on an unrelated missing packet — it’s the unavoidable price of a single connection-wide byte order, working as designed

Why can’t TCP give each stream its own sequence space to avoid HOL blocking?::TCP’s sequence space is a single flat byte stream (the decision that bought packaging freedom); no option can subdivide it

HTTP/2 fixes application-level HOL blocking with stream-ID-tagged frames, but one TCP-level loss still stalls all streams — so the blocking moved down a layer.

Beyond per-stream ordering, name QUIC’s three further wins.::Handshake collapse (1-RTT/0-RTT with TLS 1.3), connection migration (Connection ID survives address change), and escaping ossification (encrypted header)