Loading...
Loading...
Connection protocols, head-of-line blocking, and QUIC's stream-level multiplexing
DNS just handed you 93.184.216.34 with a 300-second TTL. Now you must send something across the internet to it. Two philosophies exist. One says confirm every parcel arrived, resend what got lost, and deliver everything in order, like registered mail with tracking. The other says fling it out the door as fast as possible and never look back, like a postcard. No tracking, no resends, no waiting.
The first philosophy is TCP, meaning Transmission Control Protocol, the reliable ordered byte stream. The second is UDP, meaning User Datagram Protocol, the fire-and-forget packet service. Every video call, bank transfer, and game you have ever used picked one of them, and the choice shapes everything about how the app behaves when the network gets rough.
The careful one. Nothing lost, nothing out of order.
Before a single byte moves, both sides shake hands and agree to talk. Every chunk sent gets acknowledged, meaning the receiver confirms receipt. Anything lost gets resent. Anything early waits its turn so the app reads bytes in order.
The fast one. No promises, no waiting.
No handshake, no receipts, no resends. Each packet, called a datagram, meaning an independent message, stands alone. Some arrive, some do not, order not guaranteed. What you lose in certainty you gain in raw speed.
Three messages, each with a job. Miss the point of any one and the rest of networking gets fuzzier than it needs to be. SYN means synchronize, asking to open with a starting sequence number, while ACK means acknowledged, confirming receipt:
Client
Server
Those sequence numbers, meaning per-byte counters so each byte has a position, are the whole trick: every byte gets numbered, so gaps are detectable, resends are precise, and order restores itself. Reliability is not magic. It is bookkeeping plus timers.
| Question | TCP answers | UDP answers |
|---|---|---|
| Setup first? | Yes, handshake | No, just send |
| Lost packet? | Resent automatically | Gone forever |
| Arrival order? | Always in order | Whatever arrives, arrives |
| Speed? | Slower (all that bookkeeping) | Faster (8-byte header, no chatter) |
| Overwhelmed receiver? | Slows down politely (flow control) | Keeps firing regardless |
Flow control means the receiver advertises how much buffer remains so the sender slows down before overflowing it. Congestion control, a separate mechanism, means the sender probes for how much the network itself can carry and backs off on loss.
Ask one question: what breaks if a piece goes missing? If the answer is “everything,” with money, files, and passwords in that bucket, you want TCP. If the answer is “one frame out of sixty per second,” you want UDP, and you accept the gap.
On a 150ms trans-Pacific path, TCP's three-way handshake costs a full round trip, meaning one there-and-back, before any data flows, so 150ms is gone before the request even starts. Add TLS, meaning the encryption handshake, and the first byte waits 300–450ms. UDP pays zero: the first packet carries data immediately. That gap is why DNS, a single tiny exchange where a retry costs less than a handshake, rides UDP, and why every handshake-saving trick of the last decade (TLS 1.3, QUIC with 0-RTT, meaning data on the very first packet for returning clients) exists to claw those round trips back.
| Cost | TCP pays | UDP pays |
|---|---|---|
| Setup before data | 1 RTT (SYN, SYN-ACK, ACK) | 0, first datagram is data |
| Per-packet header | 20–60 bytes + ACK traffic | 8 bytes, no ACKs |
| One lost packet at 2% loss | Everything behind it stalls a full RTT | One frame glitches; the stream continues |
Scale note: a server holding 100k concurrent TCP connections pays per-connection kernel memory and file descriptors, meaning OS handles for each socket, whether or not data flows. At roughly tens of kilobytes each, 100k means gigabytes plus descriptor pressure. UDP servers hold no per-client connection state at all, which is why game and voice servers count capacity in packets per second, not connections.
TCP is not one behavior. It is a pile of timers and buffers, and the defaults assume a friendly datacenter. Cross-region or mobile traffic wants different settings. Each knob below trades the same thing: faster recovery against false alarms.
TCP's reliability has a dark side: one lost packet freezes everything behind it while the resend travels back, called head-of-line blocking. On lossy mobile networks at 2% loss, that stall is exactly what makes pages hang, because every multiplexed stream on the connection waits for one packet. This specific pain is why QUIC, meaning reliability rebuilt on top of UDP per stream instead of per connection, exists: a lost packet stalls only its own stream.
And UDP's freedom has a price too: congestion control is your problem now. A careless UDP flood does not slow down politely. It drowns everything sharing the pipe, including other people's TCP traffic that does back off. Real-time apps earn their speed by behaving well, measuring loss and throttling themselves instead of blasting.
Transport gets your bytes there, intact or fast, your choice, with the 1-RTT SYN cost and the 2% loss stall priced above. But bytes in transit are readable by everyone in the middle, and every new connection repays that handshake toll. Privacy and reuse come next.
TCP's handshake costs 1 RTT before data, and TLS stacks 1 to 2 more on top, so a 150ms path burns 300–450ms of ceremony. The problem QUIC attacks is that ceremony. QUIC, meaning a transport protocol that runs reliability and encryption over UDP instead of TCP, folds the transport handshake and the crypto handshake into one exchange. Classic TCP plus TLS 1.2, meaning the older encryption handshake, costs three round trips, meaning three there-and-backs, before the first byte of page data. On a 150ms intercontinental path, that is nearly half a second of pure ceremony. QUIC needs one round trip for new connections and zero for returning ones that resume a session, because the client already holds the server's parameters.
| Stack | First-visit cost | Return-visit cost |
|---|---|---|
| TCP + TLS 1.2 + HTTP/2 | 3 RTT (~450 ms at 150 ms RTT) | 2 RTT with resumption |
| TCP + TLS 1.3 + HTTP/2 | 2 RTT (~300 ms) | 1 RTT, or 0-RTT for safe replays |
| QUIC + HTTP/3 | 1 RTT (~150 ms) | 0-RTT, with data riding the first packet |
The rejected reading is that fewer round trips are the whole win. The deeper mechanism is per-stream independence. HTTP/2, meaning the multiplexed web protocol over TCP, packs many streams onto one TCP connection, so one lost packet freezes every stream until the resend arrives, which is TCP-level head-of-line blocking. QUIC gives each stream its own sequence space over UDP, so a lost packet stalls only its own QUIC stream while the rest keep flowing. On 2% lossy mobile links that difference shows up as 15–30% faster p95 page loads in published edge measurements, where p95 means 95% of loads are faster and 5% slower. The arithmetic: at 150ms RTT, saving 2 round trips saves 300ms on every cold start, before loss isolation saves anything at all.
Connection migration extends the same idea. Because QUIC identifies connections by a connection ID, meaning a stable identifier independent of IP and port, rather than the classic 5-tuple of addresses and ports, a phone switching from Wi-Fi to cellular keeps the same logical connection without re-handshaking. TCP would drop and rebuild, paying the full handshake again mid-call.
QUIC runs over UDP, and the middle of the internet has opinions about UDP. Corporate firewalls and older middleboxes, meaning intermediary filters along the path, throttle or block it, so a QUIC-only deployment watches a slice of users fail mysteriously while TCP users sail through. The rejected rollout is QUIC-only with no fallback: even a 3% UDP-blocked share means 3,000 failures per 100k users, all mysterious. Load balancers need QUIC-aware routing by connection ID, not just addresses and ports, since client IPs roam on mobile, and user-space encryption, meaning crypto done in the app rather than the OS kernel where TLS offload hardware helps, means CPU per gigabit runs hotter than tuned TCP.
Browsers to edge PoPs, meaning points of presence or nearby edge sites, is where loss and latency hurt most, and where connection migration from Wi-Fi to cellular without re-handshaking shines. That is where the round-trip savings land. Inside the private network, where loss is near zero and bandwidth is free, plain TCP with keepalive, meaning idle probes that reap dead sockets, stays simpler and cheaper per gigabit.
Happy Eyeballs-style racing, meaning trying QUIC first and falling back to TCP within about 250ms, keeps the UDP-blocked minority working. Monitor the share of connections that fall back, because a sudden jump means a network or balancer regression, not an app bug. Start with idempotent GETs, meaning safe-to-repeat reads, on 0-RTT, and keep writes on 1-RTT until anti-replay, meaning edge defenses against replayed first packets, is proven, because 0-RTT data can be replayed by an attacker.
QUIC cut the ceremony to 1 RTT for new visits and 0-RTT for returning ones, with loss isolated per stream and phones roaming on one connection ID. But most backend traffic still rides TCP, and every byte on either transport travels readable without encryption. How do you make those bytes private, and how do you stop repaying the handshake toll on every fresh connection?