Difficulty: Advanced
Compare HTTP/1.1, HTTP/2 and HTTP/3. What problem does each version solve, and what is head-of-line blocking?
The history of HTTP versions is really a story of fighting latency, and if you tell it as a story rather than a feature list you will remember it and sound senior. Each version fixed the main bottleneck of the previous one, and in doing so moved the bottleneck somewhere lower in the stack.
HTTP/1.1 (1997) is text-based. Its big improvements over 1.0 were persistent connections (keep-alive, so you do not do a new TCP handshake per request), the Host header enabling virtual hosting, chunked transfer encoding and better caching. But it has a serious limitation: on one connection, requests are answered strictly one at a time. Pipelining was specified but is broken in practice. That is application-level head-of-line blocking: one slow response holds up everything behind it. Browsers worked around this by opening around 6 parallel connections per host and developers used hacks like domain sharding, sprite sheets and file concatenation. Headers are also sent uncompressed and repeated on every request, including big cookies.
HTTP/2 (2015) keeps the same semantics (methods, status codes, headers) but changes the wire format to binary framing. The main features are multiplexing, where many request and response streams are interleaved on a single TCP connection, which removes the need for multiple connections and sharding; HPACK header compression, which removes redundant headers; stream prioritisation; and server push (now mostly deprecated and removed from Chrome). Browsers only support it over TLS in practice, negotiated through ALPN during the handshake. But HTTP/2 moved the problem down a layer: since all the streams share one TCP connection, a single lost packet stalls all streams until TCP retransmits it, because TCP delivers bytes in order. That is transport-level head-of-line blocking, and on lossy mobile networks HTTP/2 can be worse than HTTP/1.1 with several connections.
HTTP/3 (2022) solves that by abandoning TCP. It runs over QUIC, a transport protocol built on UDP. QUIC implements streams natively, so packet loss in one stream affects only that stream, not the others. It integrates TLS 1.3 into the transport handshake, so a new connection needs one round trip instead of the two or three needed for TCP plus TLS, and with 0-RTT resumption a returning client can send data in the very first flight (with replay attack caveats). QUIC connections are identified by a connection id instead of the IP and port 4-tuple, so a phone switching from Wi-Fi to mobile data keeps its connection alive, called connection migration. Congestion control and loss recovery run in user space, so they can evolve faster than kernel TCP stacks, and encryption of even transport headers prevents middleboxes from interfering.
Practical details: browsers discover HTTP/3 support through the Alt-Svc response header or the HTTPS DNS record, connect first over TCP, and then upgrade. Servers must allow UDP port 443, and some corporate firewalls block UDP, so clients fall back to HTTP/2. HTTP/3 uses QPACK for header compression, which is a variant of HPACK designed to work with out-of-order delivery.
To wrap up in one line: HTTP/1.1 got persistence, HTTP/2 got multiplexing over one TCP connection, HTTP/3 got multiplexing without TCP's head-of-line blocking. A caution for interviews: do not claim HTTP/2 is always faster. For a single tiny request or very lossy networks the difference is small or negative, and the benefit shows when a page loads dozens of resources.
$ curl -sI --http2 https://www.cloudflare.com | head -1
HTTP/2 200
$ curl -sI --http3 https://www.cloudflare.com | head -1
HTTP/3 200
$ curl -sI https://www.cloudflare.com | grep -i alt-svc
alt-svc: h3=":443"; ma=86400
Alt-Svc advertises that HTTP/3 is available on UDP port 443.
HTTP/1.1, HTTP/2, HTTP/3, QUIC, Head-of-Line Blocking