# HTTP/2 Multiplexing: One Connection, Many Streams

> HTTP/1.1 made you choose between head-of-line blocking and opening a pile of connections. HTTP/2 fixes it by carrying many independent streams over a single connection — and then TCP reintroduces the very problem it solved.

URL: https://harshith.in/blog/http2-multiplexing/
Author: Harshith Sunku (Staff Engineer) — https://harshith.in/about/
Tags: networking, web

Under HTTP/1.1, a connection handles one request at a time. A slow response
blocks everything behind it — head-of-line blocking — so browsers worked around
it by opening six connections per origin and praying. HTTP/2 replaces the hack
with real multiplexing. Then it runs into a wall TCP built.

## HTTP/1.1's Dilemma

One request, one response, in order, per connection:

```text
   conn:  [── req A ──][ wait ][── req B ──][── req C ──]
                        └─ B and C stall behind a slow A
```

The six-connection workaround multiplies overhead — six TLS handshakes, six slow
starts, six congestion windows — and the browser limit means request seven still
waits.

## Streams and Frames

HTTP/2 splits the single connection into many independent **streams**, each a
request/response pair. Messages are chopped into **frames** tagged with a stream
ID, interleaved on the wire, and reassembled on the other end:

```text
   one TCP connection
   ┌──────────────────────────────────────────────┐
   │ [S1 hdr][S3 data][S1 data][S5 hdr][S3 data]…  │
   └──────────────────────────────────────────────┘
        frames from different streams, interleaved
```

Now a slow response on stream 1 doesn't block stream 3 — their frames just
interleave. One connection, one handshake, one warm congestion window, and
effectively unlimited concurrent requests. The application-layer head-of-line
problem is gone.

## The Header Win

HTTP headers are repetitive — the same `User-Agent`, `Accept`, and cookies on
every request. HTTP/2 adds **HPACK**, which keeps a shared dynamic table of
previously sent headers so repeats become small references instead of full text.
On a page firing 100 requests, that's a real saving.

## Where TCP Bites Back

Here's the twist: HTTP/2 multiplexes streams, but they all ride one TCP
connection, and TCP guarantees in-order byte delivery. If a single packet is
lost, TCP holds back *every* stream's bytes until the retransmission arrives —
even streams that were complete:

```text
   packet for S1 lost  →  TCP buffers everything after it
                          S3, S5 data sits ready but undelivered
```

The head-of-line blocking moved down a layer, from HTTP to TCP. On a clean
network you never notice; on a lossy mobile link it hurts.

This is exactly the problem **HTTP/3** solves by moving to QUIC over UDP, where
each stream has independent delivery and one lost packet only stalls its own
stream.

## Takeaways

- HTTP/2 carries many independent streams over one connection by interleaving
  frames, killing application-layer head-of-line blocking.
- One connection means one handshake, one congestion window, and HPACK header
  compression — a big win over HTTP/1.1's connection-juggling.
- TCP's in-order delivery reintroduces head-of-line blocking on packet loss,
  which is the motivation for HTTP/3 over QUIC.
