Each HTTP revision exists to fix a specific bottleneck in the previous one, and tracing that lineage is exactly what interviewers expect when they ask you to justify a protocol choice for a latency-sensitive service. The three versions aren't really "faster, faster again" — each one solves a structurally different problem, and knowing which layer each fix lives at (application vs transport) is the actual signal being tested.
These are three generations of the same web protocol, each redesigned to fix a specific slowdown in the one before it — not just "newer and faster" for its own sake.
Picture a single-lane road (HTTP/1.1) where cars must go one at a time — so the city just builds several parallel single-lane roads to keep traffic moving. HTTP/2 widens that into one road with multiple lanes side by side sharing the same surface. HTTP/3 goes further and gives each lane its own independent surface entirely, so if one lane hits a pothole, it doesn't back up traffic in the others.
"Head-of-line blocking" gets thrown around loosely — there are actually two distinct kinds, and the HTTP/2 → HTTP/3 jump exists specifically to fix the second one, not the first.
HTTP/1.1 (one request in flight per connection):
Connection 1: [--- GET /a ---][--- GET /b ---][--- GET /c ---]
request b can't even START until a's response finishes
HTTP/2 (multiplexed streams, single TCP connection):
Stream 1 (/a): [--f1--][--f1--] [--f1--]
Stream 2 (/b): [--f2--][--f2--]
Stream 3 (/c): [--f3--][--f3--][--f3--]
→ interleaved at the frame level, no app-level blocking...
→ but if ONE TCP packet is lost, TCP won't deliver ANYTHING
past that point to the app, even bytes belonging to streams
that had nothing in the lost packet — that's transport-level HOL.
HTTP/3 (QUIC over UDP, independent streams):
Stream 1 (/a): [--f1--][--f1--] [--f1--]
Stream 2 (/b): [--f2--][XX lost][--f2--] ← only this stream stalls
Stream 3 (/c): [--f3--][--f3--][--f3--] ← unaffectedHTTP/1.1's fix was purely a workaround, not a solution: browsers open 6+ parallel TCP connections per origin so one slow response doesn't block everything, at the cost of 6x the TCP/TLS handshake overhead and connection state. HTTP/2 solved the application-layer version of the problem — many logical requests share one connection via multiplexed, interleaved frames — but because it still rides on TCP, TCP's in-order-delivery guarantee means a single lost packet stalls the reassembly of every stream until that packet is retransmitted. HTTP/3 fixes this at the root by replacing TCP with QUIC, where each stream has independent loss recovery — a loss in one stream's packets never blocks delivery of another stream's already-arrived data.
HTTP/1.1 or HTTP/2 over TCP+TLS 1.3 (typical):
TCP handshake (1 RTT) → TLS 1.3 handshake (1 RTT) → data
= 2 round trips before the first byte of a response
HTTP/3 over QUIC (fresh connection):
QUIC handshake, transport + TLS 1.3 combined (1 RTT) → data
= 1 round trip before the first byte
HTTP/3 over QUIC (0-RTT resumption, returning client):
data sent immediately with the first flight
= 0 round trips before data starts flowing (idempotent requests only)QUIC doesn't bolt TLS onto a separate transport handshake the way TCP does — the transport and cryptographic handshakes are combined into the same exchange, which is where a meaningful chunk of HTTP/3's latency advantage comes from on a fresh connection, independent of the HOL-blocking fix.
# Force curl to use HTTP/2 and confirm the negotiated protocol
curl --http2 -v https://example.com 2>&1 | grep -i "HTTP/2"
# Force HTTP/1.1 for comparison
curl --http1.1 -v https://example.com
# Try HTTP/3 (requires a build of curl compiled with QUIC support)
curl --http3 -v https://example.com
# In Chrome DevTools: Network tab → right-click the column header →
# enable the "Protocol" column — shows h2, h3, or http/1.1 per request
# Inspect ALPN negotiation directly during the TLS handshake
openssl s_client -connect example.com:443 -alpn h2 -servername example.com
Q: Why was HTTP/3 introduced when HTTP/2 already solved multiplexing?
A: HTTP/2's multiplexing is an application-layer fix — it solved head-of-line blocking between logically independent requests sharing a connection. But it still rides on TCP, and TCP guarantees in-order byte delivery across the whole connection, so a single lost packet stalls every multiplexed stream until it's retransmitted, not just the stream that packet belonged to. HTTP/3 replaces TCP with QUIC, where each stream has independent loss recovery, so packet loss only affects the stream it happened on. It's also faster to establish — QUIC combines the transport and TLS 1.3 handshakes into one round trip, versus a separate TCP handshake followed by a TLS handshake.
Q follow-up: So is HTTP/3 strictly better — should you always use it?
A: Not automatically. Its advantage is largest on lossy or high-latency networks — mobile, satellite, congested Wi-Fi — where avoiding transport-level HOL blocking actually matters. On a clean, low-loss datacenter or wired network the gap versus HTTP/2 is marginal. Operationally, QUIC runs over UDP, and some corporate firewalls, proxies, and middleboxes still block or heavily throttle UDP, which complicates a clean rollout — most production deployments run HTTP/3 as an opportunistic upgrade (advertised via an Alt-Svc header) with HTTP/2 as the fallback, not as a hard replacement.
Q: What's actually different about how HTTP/2 and HTTP/1.1 handle headers?
A: HTTP/1.1 sends headers as plaintext on every single request, including large, mostly-repeated ones like cookies and user-agent strings. HTTP/2 uses HPACK, which maintains a shared compression table between client and server across the life of the connection — headers seen before are referenced by index instead of resent in full, which meaningfully cuts overhead on connections with many small requests.
Q: Does HTTP/2's server push still matter?
A: In practice, no — it's been deprecated by most major browsers (Chrome removed support). It let a server proactively send resources the client hadn't requested yet, but it was hard to get cache-coordination right and often pushed resources the client already had cached, wasting bandwidth. Modern approaches favor <link rel="preload"> or 103 Early Hints instead.
This comes up whenever you're asked to design something latency-sensitive on mobile networks — live video, a chat app, a global API serving users on variable-quality connections. The correct answer volunteers HTTP/3's resilience to packet loss as the reason to prefer it there, while acknowledging HTTP/2 multiplexing is usually sufficient for a typical low-latency, wired-datacenter-to-datacenter API. It also ties directly into CDN and load balancer design: most CDNs now negotiate HTTP/3 with the client at the edge while speaking HTTP/1.1 or HTTP/2 (or even gRPC over HTTP/2) internally to origin — the protocol version is a per-hop decision, not something that has to be uniform across your whole system.
HTTP/2 is a drop-in upgrade over existing TCP infrastructure, so it's operationally simple; HTTP/3 needs UDP to flow cleanly end-to-end, and some corporate firewalls and middleboxes still block or throttle UDP, complicating rollout. HTTP/3's latency win is largest on lossy, high-latency networks (mobile, satellite) — on a clean, low-loss network the gap versus HTTP/2 is marginal. The interview gotcha to get right: multiplexing in HTTP/2 solves head-of-line blocking at the application layer, but only QUIC's independently-recovered streams in HTTP/3 solve it at the transport layer — conflating the two is the single most common mistake on this topic.