AlgoInsight

System Design

HTTP/1.1 vs HTTP/2 vs HTTP/3

The protocol of the web has been quietly reinvented twice. Each version solved a real bottleneck the last one couldn't. Here's what changed across HTTP/1.1, HTTP/2, and HTTP/3 - and why it matters for performance.

By Admin2026-06-1814 min readhttphttp2http3networkingperformancesystem-design

HTTP is the protocol of the web - the language browsers and servers use to exchange requests and responses. It feels like a fixed, unchanging thing, but it's actually been reinvented twice in pursuit of speed. HTTP/1.1 served the web for decades, HTTP/2 overhauled how data moves over a connection, and HTTP/3 changed the foundation underneath entirely. Each version solved a real bottleneck the previous one couldn't, and understanding the progression explains a lot about web performance. This guide walks through all three: what each does, what problem it solved, and how they compare.

HTTP/1.1: one thing at a time

HTTP/1.1 was the workhorse of the web for a very long time, and it's beautifully simple: it's a text-based protocol where a client sends a request and the server sends back a response, over a TCP connection. Human-readable, easy to debug, straightforward.

Its core limitation is that, on a single connection, requests are handled essentially one at a time. A browser sends a request and largely waits for the response before the next can proceed on that connection. This causes head-of-line blocking at the application level: if one response is slow, the requests behind it wait. Since a modern web page needs dozens or hundreds of resources (images, scripts, stylesheets), this serialization is a real bottleneck.

The workaround browsers used was to open multiple parallel connections to a server (typically around six) to fetch resources concurrently. This helped, but opening many connections has its own overhead, and six is a small number against hundreds of resources. HTTP/1.1 was showing its age as web pages grew heavier - it simply wasn't built for the modern web's volume of small requests.

HTTP/2: many things at once

HTTP/2 was designed to fix HTTP/1.1's concurrency problem, and its central innovation is multiplexing. Over a single connection, HTTP/2 can handle many requests and responses at the same time, interleaved. Instead of one-at-a-time (or a handful of parallel connections), one HTTP/2 connection carries many concurrent streams of data. This eliminates the application-level head-of-line blocking of HTTP/1.1 and removes the need to open many connections.

To enable this, HTTP/2 made several changes:

HTTP/2 dramatically improved performance for the modern web by letting one connection do the work that previously took many, without the one-at-a-time bottleneck. But it had a subtle remaining flaw, rooted in the layer beneath it.

graph TD
    subgraph HTTP1["HTTP/1.1"]
    A1[Request 1] --> A2[wait] --> A3[Request 2] --> A4[wait]
    end
    subgraph HTTP2["HTTP/2: multiplexed over one connection"]
    B[One connection] --> B1[Stream 1]
    B --> B2[Stream 2]
    B --> B3[Stream 3]
    end
This diagram contrasts HTTP/1.1 and HTTP/2. In HTTP/1.1, requests on a connection happen largely one at a time - request one, then wait, then request two, then wait - causing serialization. In HTTP/2, a single connection is multiplexed into many concurrent streams - stream one, two, and three all flowing at once over the same connection - so many requests and responses proceed in parallel without waiting on each other.

HTTP/2's remaining problem: TCP head-of-line blocking

HTTP/2 multiplexed many streams over one connection - but that connection still ran over TCP. And TCP guarantees in-order delivery of all its data. So if a single packet is lost, TCP holds up everything behind it until that packet is retransmitted - including all the other multiplexed streams, even though they're logically independent.

This is TCP head-of-line blocking, and it's subtle: HTTP/2 solved head-of-line blocking at the application layer (streams don't block each other in HTTP/2's logic), but the transport layer (TCP) reintroduced it, because TCP doesn't know the streams are independent - it just sees one ordered byte stream, and one lost packet stalls all of it. On a lossy network, this undercuts HTTP/2's multiplexing benefit. To truly fix it, you'd have to change the transport itself - which is exactly what HTTP/3 did.

HTTP/3: a new foundation

HTTP/3 makes a radical change: it abandons TCP as its transport and runs over QUIC, a new transport protocol built on top of UDP. This sounds drastic, but it's the only way to eliminate TCP's head-of-line blocking - by not using TCP.

QUIC re-implements the reliability and ordering that applications need, but does so per-stream and with awareness of the independent streams, so a lost packet in one stream doesn't block the others. This finally delivers on HTTP/2's multiplexing promise even on lossy networks: independent streams are genuinely independent, all the way down. Building on UDP (which is connectionless and has no built-in ordering) gave QUIC the freedom to design exactly the behavior it wanted, rather than being constrained by TCP's strict global ordering.

HTTP/3 / QUIC brought other improvements too:

HTTP/3 is the current cutting edge, eliminating the last head-of-line blocking, speeding up connection setup, and improving mobile experience - by rebuilding the transport from UDP up.

The three compared

HTTP/1.1 HTTP/2 HTTP/3
Format Text Binary Binary
Concurrency One at a time per connection Multiplexed streams Multiplexed streams
Transport TCP TCP QUIC (over UDP)
Head-of-line blocking Yes (application level) At TCP level only Eliminated
Connection setup TCP + TLS handshakes TCP + TLS handshakes Combined, faster
Header compression No Yes Yes
Key advance The baseline Multiplexing over one connection New transport, no HoL blocking

Why HTTP/1.1 lasted - and the hacks it forced

HTTP/1.1 served the web for around fifteen years, and understanding why it lasted (and what pain it caused) makes the later versions' improvements concrete. The protocol was simple and worked, but as pages grew from a few resources to hundreds, its one-request-at-a-time limit forced developers into a set of workarounds - optimizations that existed purely to route around HTTP/1.1's constraints:

These were clever, but they were hacks - extra build and architecture complexity, all to compensate for the protocol's serial nature. When HTTP/2 arrived with multiplexing, most of these workarounds became unnecessary (and some even counterproductive). That's a good way to appreciate what HTTP/2 changed: a whole genre of web-performance tricks existed solely because HTTP/1.1 couldn't do many requests at once, and HTTP/2 made that genre largely obsolete.

Adoption, fallback, and compatibility

A practical point: these versions coexist, and clients and servers negotiate which to use. When a browser connects, it and the server agree on the highest version both support, falling back gracefully - a server supporting HTTP/2 or HTTP/3 uses it with capable clients and falls back to HTTP/1.1 for older ones. So adopting a newer version doesn't break older clients; it's an upgrade for those who can use it.

This is why you can enable HTTP/2 or HTTP/3 without leaving anyone behind - the negotiation handles it. HTTP/2 is now very widely supported; HTTP/3 is newer with growing adoption across major browsers, CDNs, and large services. In practice, much of the benefit arrives "for free" when you use a modern CDN or web server that supports the newer protocols - the CDN often handles HTTP/2 and HTTP/3 to the user while talking to your origin over whatever it supports. So a lot of real-world adoption happens at the CDN/edge layer without application changes.

A closer look at QUIC

Since HTTP/3's whole advantage comes from QUIC, it's worth understanding why building on UDP was the move. TCP is implemented deep in operating systems and is hard to change - its behavior (including the strict global ordering that causes head-of-line blocking) is baked in everywhere. By building QUIC on top of UDP (which is minimal and just delivers datagrams), QUIC's designers got a blank slate: they could implement reliability, ordering, and congestion control in QUIC itself, exactly how they wanted, rather than inheriting TCP's constraints. This is why QUIC can make streams genuinely independent (no cross-stream blocking) - it built ordering per-stream rather than globally.

It also let QUIC integrate encryption directly and combine the connection and security handshakes, cutting setup round-trips. And because a QUIC connection is identified by a connection ID rather than being tied to the IP-and-port the way TCP is, it can survive a network change (Wi-Fi to cellular) without re-establishing - connection migration. None of this would have been possible while staying on TCP. So "HTTP/3 runs on UDP" isn't a quirk; it's the enabling decision that made every HTTP/3 improvement achievable.

What this means for you

For a developer or architect, the practical implications of the HTTP evolution:

The headline for practitioners: the protocol got dramatically better, you mostly get those gains by running modern servers and CDNs, and the old workarounds can be retired.

Where HTTP sits, and what it carries

It's worth placing HTTP in the stack to see what's changing and what isn't across versions. HTTP is an application-layer protocol - the language of requests and responses (GET this page, POST this data, here's the response). That semantic layer - the methods, the status codes, the headers, the idea of requests and responses - stays essentially the same across HTTP/1.1, /2, and /3. What changes between versions is how that conversation is transported: the framing (text vs binary), the concurrency (serial vs multiplexed), and the transport beneath (TCP vs QUIC).

This is a useful distinction because it means upgrading HTTP versions doesn't change how you write web applications - a GET request is a GET request in all three. Your APIs, your request methods, your status codes all work the same. What changes is the efficiency of delivering them. So the HTTP evolution is fundamentally about transport efficiency, not about changing the web's request-response model. The application semantics are stable; the plumbing underneath got faster. That separation - stable semantics, evolving transport - is exactly why these upgrades could happen without breaking the web, and why you benefit from them largely transparently. You keep speaking the same HTTP; it just travels better.

The throughline of the evolution

Step back and the progression tells a clear story, each version solving what the last couldn't:

Each step removed the previous version's bottleneck, and the through-line is the relentless pursuit of getting many resources delivered concurrently and quickly. The deepest lesson is that solving HTTP/2's remaining problem required changing the layer beneath HTTP - you can't fix transport-level blocking at the application level, so HTTP/3 went all the way down to the transport.

Interview: HTTP versions come up in system design and web-performance discussions. The strong answer is the evolution and why of each: HTTP/1.1 was serial (one request at a time per connection); HTTP/2 added multiplexing (many streams over one connection) but still suffered TCP head-of-line blocking; HTTP/3 moved to QUIC over UDP to eliminate that and speed up connection setup. Knowing that HTTP/3's key insight was changing the transport (because the problem lived below HTTP) shows real depth. Mentioning multiplexing, head-of-line blocking, and the TCP-to-QUIC shift signals you understand web performance at the protocol level.

The bottom line

HTTP has evolved through three versions, each solving the last one's bottleneck. HTTP/1.1 is simple and text-based but handles requests largely one at a time per connection, serializing the many resources a modern page needs. HTTP/2 introduced multiplexing - many concurrent streams over a single binary connection, plus header compression - eliminating application-level serialization, but left TCP head-of-line blocking (one lost packet stalls all streams). HTTP/3 rebuilt the transport on QUIC (over UDP), eliminating head-of-line blocking entirely, speeding up connection setup by combining handshakes, and enabling connection migration for mobile.

The evolution is a steady push to deliver many resources concurrently and fast, culminating in HTTP/3's insight that the remaining bottleneck lived in the transport layer beneath HTTP. Understand the progression, and web performance at the protocol level makes sense. We cover web protocols and performance across System Design Fundamentals.


Go deeper

More articles