← System Design Simulator

HTTP/1.1 · 2 · 3

By Rahul Kumar · Senior Software Engineer · Updated · Category: Networking + Web Security

Head-of-line blocking across 6 resources under packet loss.

This interactive explanation is built for system design interview prep: step through HTTP/1.1 · 2 · 3, watch the internal state change, and connect the concept to real distributed-system trade-offs.

Overview

HTTP has been rewritten twice in a decade and each version solves a specific problem with the one before it. HTTP/1.1 (1997) is a text protocol with one request per connection — or with keep-alive, one request at a time per connection. Browsers worked around this by opening 6 parallel connections per host, and the server took a TCP handshake per connection. HTTP/2 (2015) is a binary, multiplexed protocol on a single TCP connection: many requests and responses interleave as streams, with per-stream flow control and header compression (HPACK). HTTP/3 (2022) is HTTP/2 semantics over QUIC, a UDP-based transport with built-in TLS 1.3 and 0-RTT resumption. HTTP/3 eliminates TCP head-of-line blocking by running each stream on its own QUIC flow. Knowing which version is negotiated — via ALPN on TLS or Alt-Svc headers — and what the version's limits are is essential to sizing connection pools, setting timeouts, and debugging tail latency.

HTTP/1.1 · 2 · 3 — Interactive Simulator

Runs fully client-side in your browser; no sign-up. Or open full screen →

Launch the interactive HTTP/1.1 · 2 · 3 widget — step through the algorithm or protocol and observe the internal state updating in real time.

How it works

HTTP/1.1 request lifecycle: open TCP (1 RTT), TLS handshake if HTTPS (1 more RTT for TLS 1.3), send text request, receive text response, keep the connection open for the next request on the same host. Headers are verbose and repeated on every request. Pipelining exists in the spec but is effectively dead because any out-of-order response breaks the model. HTTP/2 runs on the same TCP + TLS substrate but the wire format is framed: each HTTP message is one or more frames (HEADERS, DATA, etc.) tagged with a stream ID. Many streams coexist on one connection; each is independently flow-controlled. Headers are compressed with HPACK — a stateful encoder that replaces repeated strings with indexes into a dynamic table — so a 700-byte cookie header costs 2 bytes on the second request. The catch: TCP is still one ordered stream, so a single lost packet pauses every HTTP/2 stream on that connection (head-of-line blocking). HTTP/3 fixes this by replacing TCP with QUIC, which runs over UDP and carries its own reliability, congestion control, and encryption. Each HTTP/3 stream is a QUIC stream with independent reliability; one lost packet only blocks its own stream. QUIC also includes 0-RTT resumption and connection migration (a phone switching from WiFi to cellular keeps the same connection ID). Java's built-in HttpClient negotiates HTTP/2 via ALPN automatically when you request HTTP_2; HTTP/3 requires third-party libraries today. Multiplexing changes how you tune servers: fewer connections, much higher stream concurrency per connection.

Implementation

HttpClient with HTTP/2 via ALPN
public class Http2Client {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_2)   // negotiate via ALPN, fall back to 1.1
            .connectTimeout(Duration.ofSeconds(5))
            .build();

        HttpRequest req = HttpRequest.newBuilder(URI.create("https://www.example.com/"))
            .header("User-Agent", "http2-demo/1.0")
            .header("Accept-Encoding", "gzip")
            .GET().build();

        HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());
        System.out.println("negotiated: " + resp.version()); // HTTP_2 if both sides support it
        System.out.println("status: " + resp.statusCode());
        resp.headers().map().forEach((k, v) -> System.out.println(k + ": " + v));
    }
}
Parallel requests on one HTTP/2 connection
public class Http2Parallel {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_2)
            .build();

        List<URI> urls = List.of(
            URI.create("https://api.example.com/a"),
            URI.create("https://api.example.com/b"),
            URI.create("https://api.example.com/c"),
            URI.create("https://api.example.com/d")
        );

        // All four requests multiplex on a single TCP + TLS connection.
        List<CompletableFuture<HttpResponse<String>>> futures = urls.stream()
            .map(u -> HttpRequest.newBuilder(u).GET().build())
            .map(r -> client.sendAsync(r, HttpResponse.BodyHandlers.ofString()))
            .toList();

        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        futures.forEach(f -> System.out.println(f.join().statusCode()));
    }
}

Complexity

Key design decisions & trade-offs

Common pitfalls

Interview follow-ups

Recommended reading

Related