The Supermarket Queue
Imagine a single checkout lane. The shopper at the front has a problem with their payment, and everyone behind them — however small their baskets — simply waits. None of those later shoppers could be served until the one at the head of the line clears, even though nothing is wrong with them. That is head-of-line blocking: the item at the head of a queue is stuck, so the whole queue stalls.
This pattern appears all over networking, but it bites hardest in how browsers fetch the dozens of resources that make up a single web page. The history of HTTP is, in large part, the history of attacking this one problem at progressively lower layers.
HTTP/1.1: Blocking at the Request Layer
Under HTTP/1.1, a connection handles one request and its response at a time. The browser sends a request, waits for the full response, then sends the next. If the first response is slow — a large image, a sluggish server endpoint — every queued request behind it on that connection is blocked. A small CSS file waiting behind a big slow response just sits there.
The workaround was brute force: browsers opened multiple parallel connections to each site (typically six). That gave a few independent lanes, but each lane still had the same single-file limitation, and every extra connection paid its own handshake and slow-start cost. It mitigated the symptom without curing the disease.
HTTP/2: Multiplexing — and a Trap
HTTP/2 introduced multiplexing: many independent streams share a single connection, and their data is interleaved so that responses no longer have to arrive in request order. At the HTTP layer, head-of-line blocking was solved — a slow response no longer blocks others, because they are separate streams on the same connection. This is covered alongside the other changes in HTTP versions.
But there was a catch hiding one layer down. HTTP/2 still runs over TCP, and TCP guarantees a single, strictly ordered byte stream. If one packet is lost, TCP holds back all data that arrives after it — across every multiplexed stream — until the missing packet is retransmitted. The streams are logically independent, but TCP cannot tell them apart, so a single dropped packet stalls them all. Head-of-line blocking did not vanish; it simply moved from the HTTP layer down to the TCP layer.
| Version | HTTP-layer blocking | TCP-layer blocking |
|---|---|---|
| HTTP/1.1 | Yes — one request at a time per connection | Yes (single ordered stream) |
| HTTP/2 | No — streams are multiplexed | Yes — one lost packet stalls all streams |
| HTTP/3 | No | No — streams are independent in QUIC |
HTTP/3 and QUIC: Independent Streams
HTTP/3 closes the remaining gap by abandoning TCP altogether. It runs over QUIC, a transport built on UDP that implements its own reliability and ordering — but per stream, not for the connection as a whole. QUIC tracks each stream's data separately, so a lost packet only stalls the one stream whose data it carried. Every other stream keeps flowing while that single packet is retransmitted.
The result is that transport-level head-of-line blocking is eliminated: the failure of one resource no longer freezes the rest. This is also why QUIC matters most precisely where packet loss is common — mobile networks and congested Wi-Fi — and why HTTP/2 vs HTTP/3 is more than a version bump.
When It Actually Matters
On a clean, low-latency wired connection with little packet loss, the difference is modest — there are few stalls to avoid. The payoff shows up on lossy or high-latency paths: mobile data, distant servers, or busy Wi-Fi, where dropped packets are routine. There, TCP-level head-of-line blocking under HTTP/2 can visibly slow a page as one lost packet repeatedly pauses many resources at once, and HTTP/3's independent streams keep the rest of the page loading. If you compare load behaviour on a strong wired link versus a weak mobile signal, head-of-line blocking is part of why the gap is wider than raw speed alone would suggest.
Frequently Asked Questions
What is head-of-line blocking?
When the first item in a queue is stuck and everything behind it must wait, even though those later items could be processed. On the web, one delayed or lost piece of data holds up unrelated responses sharing the same connection or queue.
How did HTTP/1.1 suffer from it?
HTTP/1.1 handles one request at a time per connection, so a slow response blocks every request behind it. Browsers opened several parallel connections to compensate, but each had the same limit.
Did HTTP/2 fix it?
Only partly. HTTP/2 multiplexes streams over one connection, removing HTTP-layer blocking — but because it still uses TCP, a single lost packet stalls all streams until retransmission. The problem moved to the TCP layer.
How does HTTP/3 solve it?
HTTP/3 runs over QUIC, which tracks each stream independently on top of UDP. A lost packet stalls only its own stream while the others keep flowing, removing transport-level head-of-line blocking.
Does it affect everyday browsing?
Most on lossy or high-latency connections like mobile and Wi-Fi, where dropped packets are common and TCP-level blocking can noticeably slow page loads. HTTP/3 helps most in exactly those conditions.