Phase 2
HTTP/1.1, HTTP/2, and HTTP/3
See how each newer HTTP version helps websites load and respond more smoothly.
Interactive example
HTTP/1.1 vs HTTP/2
See what multiplexing over one connection actually changes.
HTTP/1.1
One request at a time per connection, worked around with parallel connections.
- Requests on one connection are handled strictly in order.
- Browsers open up to 6 connections per origin to fake parallelism.
- Domain sharding split assets across subdomains just to unlock more connections.
- Headers repeat in full on every request - no shared compression.
HTTP/1.1: one request per connection, at a time
HTTP/1.1 introduced keep-alive, letting a TCP connection stay open across multiple requests instead of reconnecting each time. But within a single connection, requests are still handled strictly one at a time - the response to request 2 can't start until the response to request 1 finishes. This is head-of-line blockingBlockingA function call that stops all other work until it finishes - like a phone call where you wait in silence for an answer. The opposite of non-blocking, where you start the work and move on immediately. at the application layer: one slow request blocks every request queued behind it on that connection.
Browsers worked around this by opening multiple TCP connections to the same origin in parallel - historically six per hostname in most major browsers. Six wasn't chosen for any deep technical reason; it was a practical compromise between parallelism and the real cost of each connection (its own TCP handshake, its own TLS handshakeTLS handshakeThe exchange that happens right after a connection opens, where a client and server agree on encryption keys before any real data is sent - the reason HTTPS costs a bit more time upfront than plain HTTP., its own congestion-control ramp-up, and real memory and file-descriptor overhead on the server). Sites with many assets got around even that limit with domain shardingShardingSplitting a single large database into multiple smaller pieces, each holding a subset of the data, so no one machine has to hold or serve all of it. Usually split by some key, like user ID or region. - serving images from img1.example.com, img2.example.com, and so on, purely to unlock more parallel connections. It worked, but it was a workaround for a limitation in the protocol, not a real fix.
HTTP/2: multiplexing over one connection
HTTP/2's core change is multiplexing: many requests and responses interleave as independent streams over a single TCP connection, each broken into small frames tagged with a stream ID. The connection can carry frames from streams 1, 3, and 7 interleaved on the wire, and the receiving end reassembles each stream from its tagged frames. A slow response on stream 1 no longer blocks stream 3 from delivering.
This mostly ended domain sharding and the six-connections-per-origin workaround overnight - browsers dropped the need for parallel connections because one HTTP/2 connection now gave them the parallelism they were faking before. HTTP/2 also added HPACK header compression (headers repeat enormously across requests to the same origin - cookies, user-agent, accept headers - and HPACK maintains a shared compression table so repeated header fields aren't retransmitted in full each time) and binary framing instead of HTTP/1.1's text-based format, which is faster to parse and less ambiguous.
HTTP/2's head-of-line blocking didn't disappear, it moved
HTTP/2 solved head-of-line blocking at the application layer, but the underlying transport is still TCP, and TCP itself guarantees strictly ordered delivery of bytes. If a single packet carrying part of stream 3's data gets dropped, TCP will not deliver any later-arriving bytes to the application - including bytes belonging to streams 1 and 7 that arrived on the wire just fine - until the lost packet is retransmitted and arrives. One dropped packet, regardless of which logical stream it belonged to, stalls every multiplexed stream on that connection. This is TCP-layer head-of-line blocking, and it's worse than it sounds on lossy networks - mobile connections, congested Wi-Fi - where packet loss is common enough to make this a real, measurable problem rather than a theoretical edge case.
HTTP/3: move the transport itself off TCP
HTTP/3 addresses this by not running over TCP at all. It runs over QUIC, a transport protocol built on top of UDP. QUIC reimplements reliability and ordering itself, but per-stream instead of per-connection: a lost packet belonging to stream 3 only blocks stream 3. Streams 1 and 7 keep delivering data to the application even while stream 3 waits for retransmission. That's the specific problem HTTP/3 exists to solve - it is not a general "let's make HTTP faster" rewrite, it's targeted at eliminating transport-layer head-of-line blocking.
QUIC also folds the TLS handshake into its own connection setup (QUIC mandates TLS 1.3), so establishing a new QUIC connection combines what used to be separate TCP and TLS round trips into fewer round trips overall, and QUIC connections survive a client's IP address changing - switching from Wi-Fi to cellular mid-connection - via a connection ID that isn't tied to the underlying network path, something a TCP connection can't do without breaking and re-establishing.
Where each one actually shows up today
HTTP/1.1 is still common for simple internal service-to-service calls where multiplexing doesn't matter much. HTTP/2 is the default for most public web traffic today - virtually every CDN and major server supports it, and it requires no application code changes, just server/proxy configuration. HTTP/3 adoption is growing fast at the CDN and browser level (Cloudflare, Google, most modern browsers support it) but is less commonly something an individual backend team configures directly - it's usually handled by whatever edge/CDN layer terminates connections in front of the application.