HAMZA KAREEM / CONCEPTS hamzawi.dev

Lesson 612 min read

Head-of-Line Blocking (HTTP/1.1 vs HTTP/2 vs HTTP/3)

Web developmentweb development / networking

The myth

"We moved to HTTP/2, so head-of-line (HOL) blocking is gone — one connection multiplexes everything, problem solved."

Actually…

HTTP/2 fixed HOL blocking at the application layer but pushed it down to the transport (TCP) layer. On a lossy network it can be just as painful. HTTP/3 is what actually removes it — by ditching TCP.

The right mental model (3 stages)

  1. HTTP/1.1 — HOL at the request level

One request per connection at a time. Browsers open ~6 parallel TCP connections per origin to fake concurrency. A slow response blocks its own connection; the others keep going.

  1. HTTP/2 — one connection, many streams

All requests share ONE TCP connection, chopped into interleaved frames (streams). App-layer HOL gone: a slow response no longer blocks the others logically.

BUT TCP delivers bytes strictly in order. If ONE packet is lost, TCP holds back ALL later bytes until the retransmit arrives — even bytes belonging to unrelated streams. So every stream stalls on one lost packet. That's TCP-level HOL blocking.

  1. HTTP/3 — QUIC over UDP

QUIC runs on UDP and makes each stream independently ordered. A lost packet only stalls the stream that owned those bytes; the rest keep flowing.

A concrete number

Say 1% packet loss, 100 in-flight streams.

  • HTTP/2: that one loss stalls the whole connection → all 100 streams wait for the retransmit (~1 RTT of dead time, e.g. 100ms on mobile).
  • HTTP/3: only the 1 affected stream waits; 99 keep going.

On a clean wired network (~0% loss) HTTP/2 and HTTP/3 look nearly identical. The gap shows up on flaky Wi-Fi / cellular.

The scenario that trips people up

You benchmark HTTP/2 in the office on gigabit fiber → looks great, ship it. Real users on 4G with 2% loss report the site feels slower under load than your old HTTP/1.1 (6 connections) setup, because now a single dropped packet freezes every asset at once. The fix isn't "tune HTTP/2" — it's HTTP/3.

Practical takeaways

  • HTTP/2's win is mainly fewer connections + header compression + multiplexing on GOOD networks.
  • The bigger real-world win of HTTP/3 is resilience to loss (and 0-RTT connection setup / connection migration across IP changes).
  • HTTP/3 needs UDP open on the path; some corporate networks block it → clients fall back to HTTP/2. Always keep the fallback working.
  • Bonus: QUIC also removes the TCP+TLS handshake round trips (1-RTT, often 0-RTT resumes).

Common mistakes

  • Assuming multiplexing = no blocking. It removes app-layer HOL, not transport HOL.
  • Benchmarking only on a perfect network, where the difference is invisible.
  • Forgetting HTTP/3 can silently fall back to H2, so measure what clients actually negotiate.

Your exercise

Open Chrome DevTools → Network tab → right-click the column header → enable "Protocol". Load a big site (e.g. a CDN-heavy one) and note which requests show h2 vs h3. Then in a terminal try:

curl -sI --http3 https://cloudflare.com | head -1
curl -sI --http2 https://cloudflare.com | head -1

Which protocol did your browser negotiate for most assets — and can you find one origin that only offers h2?