Why HTTP got rewritten twice
HTTP/1.1's head-of-line blocking problem, how HTTP/2 fixed it with multiplexing, and why HTTP/3 had to leave TCP behind entirely to fix it again.

HTTP barely changed for almost 20 years, and then it got rewritten twice in under a decade. Not because the request/response idea from lesson 5 was wrong. It wasn't. It's because the way HTTP/1.1 used a connection turned out to waste most of what TCP could actually offer, and fixing that properly eventually meant leaving TCP behind altogether.
The problem: one request at a time, per connection
HTTP/1.1, the version this whole series has been describing, sends one request, waits for the full response, then sends the next. A modern page load isn't one request. Loading a typical page means fetching the HTML, then the CSS, then a dozen images, then a few scripts, maybe more. Over a single HTTP/1.1 connection, those requests queue up behind each other. If one response is slow, everything behind it in that connection waits too, a problem with a real name: head-of-line blocking.
Browsers worked around this by opening multiple TCP connections to the same server, often six at once, so six requests could be in flight in parallel. It worked, but it's a patch, not a fix. Each of those connections pays its own TCP handshake and, on HTTPS, its own TLS handshake (see the TLS lesson), all that round-trip cost multiplied by six, just to work around a limitation in how one connection was being used.
HTTP/2: one connection, many streams at once
HTTP/2, standardized in 2015, fixes this without touching TCP at all. Instead of opening six connections, the browser opens one, and multiplexes many requests over it at the same time using something called streams. Each request and response gets broken into small frames, tagged with a stream ID, and interleaved on the wire. The server can start sending the CSS response before it's even finished sending the HTML response, because they're not competing for the whole connection anymore, just taking turns at the frame level.
Notice the responses don't come back in request order. That's the whole point. Nothing is stuck waiting behind something slower on the same connection anymore. HTTP/2 also compresses headers (many requests to the same site repeat nearly identical headers) and lets a server push resources it knows you'll need before you even ask, though that push feature saw limited real-world adoption and Chrome eventually dropped it.
Quick check
A page makes 20 requests over a single HTTP/2 connection. Request #3 is slow to respond. What happens to requests #4 through #20?
HTTP/3: the fix that had to go deeper than HTTP
HTTP/2 fixed head-of-line blocking at the HTTP level. It turned out there was still head-of-line blocking underneath it, at the TCP level, and HTTP/2 couldn't touch that.
Here's the problem: TCP itself guarantees strict, in-order delivery of every byte on a connection (see the TCP lesson). If a single packet gets lost, TCP won't hand any later data to the application, even data belonging to a completely unrelated HTTP/2 stream, until that lost packet gets retransmitted and the gap is filled. HTTP/2's independent streams are still all riding inside one TCP connection, and TCP doesn't know or care about streams. One dropped packet stalls everything, exactly the problem multiplexing was supposed to solve, just moved down a layer.
Fixing that meant HTTP/3 couldn't be built on TCP at all. It's built on QUIC, a newer transport protocol that runs over UDP (the connectionless, no-guarantees protocol from the TCP vs UDP lesson) and reimplements reliability itself, but per-stream instead of per-connection. QUIC tracks loss and retransmission separately for each stream, so a lost packet belonging to one stream only stalls that stream. Everything else keeps flowing.
QUIC also builds TLS 1.3 directly into its handshake, rather than layering it on top the way HTTP/2 does over TCP. That's what lets HTTP/3 open a fully encrypted, ready-to-use connection in a single round trip, and on a connection it's seen before, sometimes zero.
You've probably already used HTTP/3 today
Most major browsers and CDNs support HTTP/3 by default now, and it negotiates automatically, your browser and the server agree on it during the connection setup with no visible change to you. Open your browser's dev tools, check the Network tab's Protocol column on a site like YouTube or Cloudflare, and you'll likely see h3 sitting right there.
The shape of the fix, twice
Both rewrites follow the same pattern: find where requests are stuck waiting behind each other for no good reason, and remove that dependency. HTTP/2 removed it at the application layer by multiplexing streams over one TCP connection. HTTP/3 removed it one layer further down, because TCP's own guarantees turned out to be the thing still causing the wait. Neither rewrite changed what an HTTP request or response actually looks like: the methods, status codes, and headers from lessons 5 and 6 are exactly the same on HTTP/3 as they were on HTTP/1.1. What changed both times was purely how efficiently that conversation travels.
The web isn't stateless in practice, even though HTTP itself has no memory between requests. The next lesson covers the mechanism that fixes that. On to Cookies and how sites remember you.

Written by
Rhythm Bhiwani
Engineer and relentless builder, happiest reverse-engineering hard problems until they click.
Enjoyed this?
Tap the heart to leave some love.
Be the first to react
Comments
Join the conversation.
Loading comments…


