Heads up: posts on this site are drafted by Claude and fact-checked by Codex. Both can still get things wrong — read with care and verify anything load-bearing before relying on it.
why → how

Why does QUIC exist when TCP already works?

TCP works fine — until you're on a flaky phone connection, juggling a dozen multiplexed streams, and one lost packet stalls all of them. QUIC is the protocol designed around that specific frustration.

Networking intermediate Apr 29, 2026 · updated Aug 25, 2026 · 12 min read

On this page

The picture version

Six pictures for a reader who has never thought about what a connection is. The prose below fills in the seams the pictures skip.

1 · The problem

A TCP connection is its four addresses. Change one and it’s dead.

(src IP, src port, dst IP, dst port) this tuple isn’t a label on the connection — it is the connection on home Wi-Fi one address you walk out the door on mobile data a different address connection gone Not a bug. The definition.
That is one of three pressures on a protocol designed for a world that no longer exists. The other two: a modern page pulls dozens of resources at once, and encryption went from optional to expected — so connecting costs a TCP handshake plus a TLS handshake before any data moves.

2 · The second pressure

One lost packet, and everything unrelated waits behind it.

HTTP/2 multiplexes many streams over one TCP connection — but TCP knows about only one lost these arrived. the application isn’t allowed to have them yet. The stylesheet waits for a byte belonging to the image. head-of-line blocking — and multiplexing over one connection exposes it more sharply on lossy links than opening several did you could fix all of this inside TCP, in theory. the next picture is why you can’t.
TCP promises one ordered byte stream, and it keeps that promise strictly. Every stream layered above it inherits the promise whether it wants it or not — which is the problem HTTP/2 could not solve from where it sat.

3 · Why you can’t just fix TCP

The network in the middle has opinions, and they are thirty years old.

your new TCP option lives in the kernel — ship an OS to billions and then middleboxes drop what they don’t recognise routers, NATs, “smart” firewalls — thirty years of hard-coded assumptions about what a TCP packet looks like Protocol ossification: it can’t change because the network won’t let it. So QUIC stops asking permission and builds on UDP instead. UDP gives you almost nothing — “deliver this to that address, maybe” — and that is exactly the appeal: there is nothing to ossify against
Everything TCP does in the kernel — sequence numbers, acknowledgements, congestion control, retransmission — moves into a library in userspace, where an application can ship an update without waiting for an operating system to.

4 · Encrypt it, or it ossifies too

If middleboxes can’t read the fields, they can’t grow opinions about them.

TCP + TLS TCP handshake TLS handshake two handshakes, one after the other QUIC one handshake — the key schedule folded in almost every byte encrypted or authenticated from the start A deliberate anti-ossification move, not just a privacy one. “everything” is a slight exaggeration: a small outer header stays visible so packets can be routed and connections identified, and the first Initial packets use keys derived from public values, so an on-path observer can parse more of them than of later traffic the point is that the fields middleboxes historically built policy on are simply no longer available to them
Ship a readable new transport and middleboxes start parsing its fields within a few years — and then it is frozen too. Encryption is what keeps the protocol free to evolve.

5 · The two payoffs

Independent streams, and a name the network can’t take away.

each stream numbers its own bytes, so the receiver knows which stream a gap belongs to stream 1 — flowing stream 7 — waiting on its own gap stream 8 — flowing loss detection and congestion control still run on the connection as a whole — the independence is in delivery order, not bandwidth and the connection is named by an opaque ID chosen during the handshake, not by the four addresses Wi-Fi address cellular address same ID Once the endpoint validates the new path, the connection just carries on.
This is the head-of-line fix HTTP/2 fundamentally couldn’t make, because the TCP underneath only ever knew about one stream. Whether the migration actually happens depends on both ends implementing it — but TCP can’t do it at all without protocol surgery.

6 · Keep this card

The whole thing on one index card.

QUIC = UDP + TLS 1.3 + per-stream reliability + connection IDs ∴ not a faster TCP — a transport that could still be changed
Picture to keep: instead of one conveyor belt where a jammed box stops everything behind it, a bundle of independent belts sharing one motor — and the whole bundle tagged with a ticket number, so it can be picked up and plugged into a different socket without stopping. Note the shared motor: a jam on one belt no longer blocks the others, but everything still slows when the motor throttles.

Why it exists

Picture this: you’re on a video call walking out of your house. Your phone hops from home Wi-Fi to mobile data. With most older internet protocols the call would drop right there — your phone’s “address” just changed, and the connection is defined by that address. With newer ones, the call can carry on across the switch instead of being torn down. That’s QUIC at work. Same goes for the moment a single packet is lost on flaky hotel Wi-Fi: with TCP, every other transfer sharing that connection waits for the missing byte. QUIC was designed so only the transfer that actually lost something has to wait.

For decades, the answer to “how do two computers reliably exchange a stream of bytes over the internet?” has been TCP. It works. It’s everywhere. So why did Google build an experimental replacement (gQUIC), and why did that work end up as a standardized protocol at the IETF?

Because TCP was designed in a world that no longer exists. Three things changed underneath it, and the friction kept growing:

  1. Connections move now. Your phone hops between Wi-Fi and cellular mid-page-load. A TCP connection is keyed by (source IP, source port, dest IP, dest port) — change any of those four and the connection is, by definition, dead.
  2. Pages need many things at once. A modern page pulls dozens of resources in parallel. HTTP/2 tried to fix this by multiplexing many streams over a single TCP connection. But TCP is a single ordered byte stream — if one packet is lost, every stream above it has to wait. This is head-of-line blocking, and multiplexing over one TCP connection exposes it more sharply on lossy links than opening several connections did.
  3. Encryption is the default. TLS used to be optional on the web. Now browsers treat plain HTTP as an error state, and anything handling real data is expected to be encrypted. But TLS sits on top of TCP, so connecting takes a TCP handshake (one round trip) plus a TLS handshake (one or two more). On a satellite link or congested mobile, that’s painful before any actual data moves.

You could fix all of this inside TCP, in theory. In practice you can’t, because TCP lives in the kernel and middleboxes — routers, NATs, “smart” firewalls — have spent thirty years hard-coding assumptions about what TCP packets look like. Try to add a new TCP option and a chunk of the internet’s middle silently drops your packets. This is protocol ossification: the protocol can’t change because the network refuses to let it.

QUIC’s move is to side-step that entirely. Build a new transport on top of UDP, encrypt everything (including the parts middleboxes try to inspect), and put the protocol in userspace so applications can ship updates without waiting for the kernel.

Why it matters now

HTTP/3 is HTTP carried over QUIC, and the major browsers support it and negotiate it by default when the server offers it — which the large CDNs do. So if your site sits behind one of them, some fraction of your traffic is already QUIC whether or not anyone chose that; if it doesn’t, you’re still entirely on TCP.

For engineers, the practical consequences:

The short answer

QUIC = UDP + TLS 1.3 + per-stream reliability + connection IDs

Picture to keep: instead of one conveyor belt where a jammed box stops everything behind it, a bundle of independent belts sharing one motor — and the whole bundle is tagged with a ticket number, so it can be picked up and plugged into a different power socket without stopping. Note the shared motor: that’s where the analogy is exactly right and where people expect too much. A jam on one belt no longer blocks the others, but everything still slows down when the motor throttles.

QUIC is a reliable, multiplexed, encrypted transport built on UDP instead of TCP. It folds the crypto handshake into the transport handshake, gives each logical stream its own loss recovery so a dropped packet doesn’t stall its neighbors, and identifies connections by an ID rather than the source/destination 4-tuple — so the connection survives when the network path underneath changes.

How it works

Take the walk out of your front door as the case to fix, and let each attempt fail into the next one.

Naive fix: patch TCP. Add a TCP option for “this connection moved,” add one for per-stream framing. This is the obvious answer and it’s the one that doesn’t work: TCP lives in the kernel, so shipping a change means shipping an OS to billions of devices, and even then middleboxes drop or mangle options they don’t recognize. You’d wait years to find out whether the fix survived the path.

Fix: stop asking permission — build on UDP. QUIC packets ride inside UDP datagrams. UDP gives you almost nothing — just “deliver this to that IP/port, maybe” — and that’s exactly the appeal. There’s so little structure for middleboxes to ossify against. The protocol logic that TCP does in the kernel (sequence numbers, acknowledgments, congestion control, retransmission) all moves into a QUIC library in userspace.

But moving to UDP re-opens the door to the same ossification. Ship a readable new transport and middleboxes will start parsing its fields within a few years, and then it’s frozen too — plus the connection is now completely unencrypted, since UDP brings nothing. Fix: make encryption part of the transport rather than a layer above it. In TCP+TLS the transport runs first, then TLS negotiates on top. In QUIC, almost every byte of every packet is encrypted or authenticated from the start, including fields a middlebox would historically read. The handshake reuses TLS 1.3’s key schedule but folds it into the transport’s own packets. The result is one unified handshake instead of two. “Everything” is a slight exaggeration: a small outer header stays visible so the packet can be routed and the connection identified, and the first Initial packets are protected with keys derived from public values, so an on-path observer can parse more of them than of later traffic. The point is that the fields middleboxes historically built policy on are no longer available to them.

This is also a deliberate anti-ossification move. If middleboxes can’t read the fields, they can’t grow assumptions about them, and the protocol stays free to evolve.

But a single reliable stream would just recreate head-of-line blocking. If QUIC delivered one ordered byte stream like TCP, a lost packet on the video call’s control channel would still stall every other transfer sharing the connection — you’d have rebuilt the HTTP/2 problem one layer down. Fix: make streams first-class and independent. A QUIC connection carries many streams, and each stream numbers its own bytes with its own offsets, so the receiver knows which stream a gap belongs to. Lose a packet carrying stream 7’s bytes and only stream 7 has a gap to wait on; streams 1–6 and 8–N keep handing bytes up to the application. (Loss detection and congestion control still operate on the connection as a whole — the independence is in delivery order, not in bandwidth.) This is the head-of-line blocking fix, and it’s the part HTTP/2 fundamentally couldn’t do, because the TCP underneath only knew about one stream.

And none of that helps when you step onto the street. Wi-Fi to LTE changes your IP, and a connection identified by its address endpoints is dead by definition — the call drops mid-sentence. Fix: name the connection something the network can’t take away. Instead of (srcIP, srcPort, dstIP, dstPort), a QUIC connection is identified by an opaque connection ID chosen during the handshake. If your IP changes — Wi-Fi to LTE, NAT rebinding — the connection ID is still valid, and once the endpoint has validated the new path (“is this really you, and does traffic actually flow here?”) the connection can continue rather than being rebuilt. Whether that happens in practice depends on both ends implementing it; TCP can’t do it at all without protocol surgery.

Show the seams

A few things the marketing version glosses over:

You started with QUIC = UDP + TLS 1.3 + per-stream reliability + connection IDs. What did the walk out the front door add? — + UDP was chosen for what it doesn't have. Every headline feature follows from putting the transport somewhere the kernel and the middleboxes can’t freeze it: that’s why the crypto could be folded in, why streams could be split, why an ID could replace the 4-tuple — and why QUIC pays in CPU for things TCP gets from silicon and the kernel.

Check yourself

Before you go — a colleague moves an internal service from HTTP/1.1 to HTTP/3 and sees no improvement at all. The service runs inside one datacenter over a 0.2 ms link with essentially no packet loss. Why is that the expected result?

Answer

Almost everything QUIC buys you is priced in round trips and loss. Head-of-line blocking only hurts when packets are actually lost; the handshake savings only matter when an RTT is expensive; connection migration only matters when the client’s address changes. On a lossless sub-millisecond link none of those apply, and you’re left paying QUIC’s extra per-packet CPU for benefits the environment can’t deliver. QUIC is a fix for bad paths, not a universally faster transport.

And one more — if per-stream loss recovery removes head-of-line blocking, why can a lost QUIC packet still delay a stream that had nothing in it?

Answer

Streams are independent for delivery, not for capacity. All streams share one connection-level congestion controller, so a loss makes the whole connection slow down — every stream’s bytes now leave more slowly, including streams that lost nothing. What QUIC removes is the ordering dependency (you no longer wait for someone else’s missing byte), not the shared bottleneck.

Going deeper