Evolution of HTTP (0.9 → 3)
HTTP is the application-layer protocol browsers and servers use to request and deliver web content. Over time, each HTTP version tried to solve real bottlenecks such as latency, too many connections, head-of-line blocking, and slow or fragile handshakes.

HTTP/0.9 (obsolete)
HTTP/0.9 was the earliest, ultra-minimal version of HTTP. It only supported the GET method and had no headers and no status codes, so the response was typically just the raw body (often HTML) with no metadata. In practice, a client would send something like GET /index.html, and the server would reply with the HTML bytes directly.
Because it lacked headers and status codes, there was no standard way to describe content type, encoding, caching rules, authentication, or even to reliably signal errors like “not found” or “server error”.

HTTP/1.0 (obsolete, but historically important)
HTTP/1.0 is where HTTP started to look like the protocol we recognize today. It introduced request and response headers and standardized status codes (2xx, 3xx, 4xx, 5xx), and it made common methods like GET, POST, and HEAD part of typical usage.
In terms of connections, HTTP/1.0 usually used one TCP connection per request, then closed it. You’ll often see this associated with Connection: close. That approach worked for simple pages, but it became expensive as pages grew and required many assets, because repeated TCP connection setup added latency.
A typical exchange looked like a request such as GET / HTTP/1.0 with headers like User-Agent, followed by a response like HTTP/1.0 200 OK with headers such as Content-Type: text/html.

HTTP/1.1 (standard, still widely used)
HTTP/1.1 focused on reducing connection overhead and making the protocol scale better. The most important improvement was persistent connections (keep-alive) by default, which allowed multiple requests and responses to reuse the same TCP connection. It also required the Host header, which enabled virtual hosting so multiple domains could share the same IP address. It added chunked transfer encoding, allowing servers to stream responses without knowing the full size upfront, and it improved many semantics around caching, partial content, and general robustness.
Even with these improvements, HTTP/1.1 still suffered from head-of-line blocking at the application level when reusing a single connection, because responses are delivered in order on that connection. To compensate, browsers commonly opened multiple parallel TCP connections per origin (often around six) to simulate concurrency.
HTTP/1.1 also introduced pipelining, where a client could send multiple requests without waiting for each response. In practice, pipelining was rarely reliable across real-world proxies and servers, so browsers generally avoided it.

HTTP/2 (standard)
HTTP/2 aimed to eliminate the performance pain of HTTP/1.1 without changing the meaning of HTTP itself. Its big idea is that you can keep a single TCP connection but allow many concurrent request/response streams on top of it. Instead of plain text on the wire, HTTP/2 uses a binary framing layer, which makes parsing more efficient and enables more advanced features.
With multiplexing, multiple requests and responses can be interleaved over one TCP connection, removing the need for many separate connections per origin. HTTP/2 also compresses headers using HPACK, which reduces the repeated cost of sending large, similar header sets (like cookies) across many requests. It also supports stream prioritization, letting the client hint what should be delivered first, although how well this works depends on the implementation.
A key limitation is that HTTP/2 still runs over TCP. If there’s packet loss, TCP must retransmit in order, and that can stall delivery for all streams on that connection (TCP-level head-of-line blocking). HTTP/2 also introduced Server Push, but many stacks and teams moved away from it because it was difficult to tune correctly and could waste bandwidth by sending resources the browser didn’t actually need.
HTTP/3 (standard) — HTTP over QUIC
HTTP/3 keeps the benefits of HTTP/2 but changes the transport layer to avoid TCP bottlenecks. Instead of TCP, HTTP/3 runs over QUIC, which itself runs over UDP. QUIC is designed for modern web traffic and builds in features like encryption and stream management directly into the transport.
Because QUIC supports independent streams at the transport level, HTTP/3 can avoid the TCP-style head-of-line blocking problem where one lost packet can stall everything. QUIC also integrates TLS 1.3 into its handshake flow, which can reduce connection setup latency, and it can support 0-RTT resumption in some situations (with important security trade-offs). Another practical benefit is connection migration, where a connection can sometimes survive network changes, such as switching from Wi-Fi to mobile data, more smoothly than traditional TCP connections.
The trade-offs are increased complexity and different operational concerns, since QUIC stacks often live in user space rather than in the OS kernel, and some networks have historically treated UDP less favorably (though this is less common today).
Quick comparison
HTTP/1.0 introduced headers and status codes but often required many short TCP connections. HTTP/1.1 made connections persistent and improved semantics, but ordering and queueing issues remained and browsers still used multiple parallel connections. HTTP/2 added multiplexing and header compression, improving performance while still being constrained by TCP’s behavior under loss. HTTP/3 delivers multiplexing with faster handshakes and avoids TCP head-of-line blocking by using QUIC over UDP.
Practical guidance
For modern web applications, you should generally prefer HTTP/2 or HTTP/3 when available, which is commonly handled by your TLS terminator, CDN, or load balancer rather than your application code. At the same time, protocol upgrades won’t fix fundamental payload problems, so you still need to optimize caching, compression, CDN strategy, request counts, and payload size, because HTTP/3 can’t compensate for huge images or excessive JavaScript.