HTTP/1.0 vs 1.1 vs 2 vs 3 (QUIC)

intermediate http http2 http3 quic performance tcp

HTTP has gone through four major versions. Each one fixed a bottleneck of the previous one.

In simple language: HTTP/1.0 opened a new connection per request. 1.1 reused connections. HTTP/2 sent multiple things at once on one connection. HTTP/3 ditched TCP entirely.

HTTP/1.0 (1996)

One TCP connection per request. Open, send request, get response, close.

The cost: TCP handshake every single time. Loading a page with 30 images? 30 handshakes. Painfully slow.

HTTP/1.1 (1997)

Introduced persistent connections (keep-alive) by default. The TCP socket stays open and we can reuse it for multiple requests.

It also added pipelining — sending multiple requests without waiting for responses. But responses still had to come back in order, so a slow response blocked all the ones behind it (head-of-line blocking). Most browsers never enabled pipelining because of this.

To speed things up, browsers opened 6 parallel connections per domain. Better than nothing, but wasteful.

HTTP/2 (2015)

Built on Google’s SPDY. The big idea: multiplexing. One TCP connection, many concurrent streams.

Key features:

  • Binary protocol instead of text — faster to parse.
  • Stream multiplexing — multiple requests/responses interleaved on a single connection.
  • Header compression (HPACK) — repeated headers like User-Agent get compressed.
  • Server push — server can send resources the client hasn’t asked for yet (later deprecated, rarely used well).

Problem: HTTP/2 still runs over TCP. If a single TCP packet is lost, all streams pause until it’s retransmitted. TCP-level head-of-line blocking.

HTTP/3 (2022)

Same semantics as HTTP/2 but built on QUIC instead of TCP. QUIC runs on UDP.

Why this matters:

  • Each HTTP stream is an independent QUIC stream. A lost packet only stalls its own stream, not all of them.
  • TLS 1.3 is baked in. The handshake is faster — often 0-RTT for repeat visits.
  • Connection migration: switching from Wi-Fi to mobile data doesn’t drop the connection (uses a connection ID, not IP+port).

In simple language: HTTP/3 is HTTP/2’s idea done right, on a transport that doesn’t get in the way.

Side-by-Side Comparison

HTTP/1.0
Connection: new per request
HOL block: N/A (1 at a time)
Transport: TCP
Format: text
HTTP/1.1
Connection: keep-alive
HOL block: at app layer
Transport: TCP
Format: text
HTTP/2
Connection: 1 multiplexed
HOL block: at TCP layer
Transport: TCP + TLS
Format: binary + HPACK
HTTP/3
Connection: QUIC streams
HOL block: none
Transport: UDP + QUIC
Format: binary + QPACK

Checking Which Version We Use

# curl shows the negotiated version
curl -I --http2 https://www.cloudflare.com
curl -I --http3 https://www.cloudflare.com

# In Chrome DevTools → Network → Protocol column shows h2, h3, http/1.1

Interview Tip

The key story to tell: head-of-line blocking moved up the stack and finally got solved. 1.1 had it at the request level, 2 had it at the TCP level, 3 fixed it with QUIC. If you can explain that, you’ve nailed the evolution.