← Computer Networks
DNS & the Web Application Layer

HTTP/1.1 vs HTTP/2 vs HTTP/3

Watch: "HTTP 1 Vs HTTP 2 Vs HTTP 3!" — ByteByteGo

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.

What Is HTTP/1.1, HTTP/2, and HTTP/3?

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.

Where This Is Used

A Day-to-Day Example

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.

Deep Dive: Head-of-Line Blocking, Precisely

"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.

Head-of-line blocking in HTTP/1.1, HTTP/2 and HTTP/3HTTP/1.1: one request at a time per connectionconnection 1GET /aGET /bGET /cHTTP/2: streams interleaved on ONE TCP connectionstream /aaaastream /bbbstream /cccc1 lost packet stalls ALLHTTP/3 (QUIC): streams are independentstream /aaaastream /bb✕ lostbstream /cccconly stream /b waits
HTTP/1.1 makes request b wait for a's whole response. HTTP/2 interleaves frames from many streams, but they all share one TCP byte stream, so a single lost packet holds back every stream until it is resent. QUIC keeps a separate ordering per stream, so a loss only delays the stream it belongs to.
/a/blost
Show text version
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--]  ← unaffected

HTTP/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.

Deep Dive: Connection Establishment Cost

Round trips before the first byte of dataTCP + TLS 1.3TCP handshakeTLS 1.3 handshakedata2 RTT before dataQUIC, new connectionQUIC + TLS combineddata1 RTTQUIC 0-RTT (returning)data0 RTT (idempotent only)
Each block is one round trip (RTT). TCP and TLS each need their own handshake; QUIC merges them into one, and a returning client can even send data in its very first packet.
handshake round tripdata
Show text version
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.

Key Pointers

How It Works

  1. HTTP/1.1: the client opens one or more TCP connections and sends requests serially on each, waiting for a response before reusing that connection.
  2. HTTP/2: the client opens a single TCP connection; requests and responses are split into frames tagged with a stream ID and interleaved on the wire, then reassembled by the receiver.
  3. HTTP/2's HPACK compresses repeated header fields across requests on that connection, cutting overhead versus HTTP/1.1's plaintext headers on every request.
  4. HTTP/3: the client establishes a QUIC connection over UDP, performing the transport and TLS 1.3 handshake in the same exchange.
  5. Each HTTP/3 request maps to an independent QUIC stream with its own loss-recovery state, so packet loss on one stream never blocks the others.

Try It Yourself

# 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

Interview Q&A

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.

System Design Tie-in

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.

Trade-offs

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.