TCP vs UDP (and why QUIC exists)
Monday, 8:30: in Jakarta, the StayHub app takes 3.4 s to open on mobile data and a third of guests give up first. The CDN is working and our servers answer in 40 ms.
The internet loses and shuffles packets. TCP fixes that with care and patience; UDP just sends and hopes. Here's when each wins.
- Problem
Applications want to send messages across a network that drops, duplicates, delays and reorders packets.
- Why the simple system fails
Raw packets give you none of the guarantees apps assume. But adding every guarantee everywhere makes real-time traffic (calls, games) laggy and handshakes slow.
- Engineering concept
Pick the transport by what your data needs: a reliable ordered byte stream (TCP), or independent datagrams where late data is useless (UDP), or reliable independent streams (QUIC).
- Technology
TCP three-way handshake, sequence numbers, ACKs, retransmission, congestion window; UDP datagrams; QUIC with TLS 1.3 and HTTP/3.
- Trade-offs
Reliability and ordering cost latency and head-of-line blocking; UDP pushes reliability, congestion control and security work onto the application.
The problem
2 min- Maya · Product managerIn Jakarta the app takes 3.4 s to open on mobile data. A third of guests quit first.
- Omar · On-call SREOur API answers each call in 40 ms. The CDN serves 96% of photos. Nothing's red.
- Lina · Senior engineer, your mentorThen look at the road between them. Guess first.
The big idea
4 minRound trips are the network's currency. Don't pay for the same handshake six times.
A faster server would save 120 ms of 3.4 s. Moving to Asia is a big project. Paying for the greeting once, then reusing the connection, is cheap. And the choice of TCP or UDP comes down to one question: is late data still useful?
How it works
5 min- 1Best effort underneath
Data travels in small packets that can be lost, duplicated or reordered. A round trip (RTT) is one packet there and the answer back: 180 ms Jakarta ↔ Frankfurt.
- 2TCP: the careful transport
says hello first (SYN, SYN-ACK, ACK: the , 1 RTT), numbers every byte, resends losses and delivers one ordered stream.
- 3A new HTTPS call costs 3 round trips
TCP hello (1) + TLS keys (1) + request and answer (1) = 3 × 180 + 40 ms server = 580 ms. Six in a row ≈ 3.5 s, mostly handshakes.
- 4Head-of-line blocking
Head-of-line blocking: one lost packet makes everything behind it on the same TCP connection wait for the resend. On a 2% loss phone, ten photos stall for one.
- 5UDP: the quick transport
sends with no hello, no confirmations, no resending. Right when late data is useless (a live call), wrong when every byte matters (a payment).
- 6QUIC: reliable streams over UDP
, under HTTP/3, rebuilds reliability over UDP per stream, so a loss only delays its own photo, and folds the TLS hello in: 2 RTT on a new connection.
Watch a decision being made
4 minLina fixes app start for Jakarta
The app takes 3.4 s to open in Jakarta (RTT 180 ms, server 40 ms). Cut it without moving any servers.
- 1Count the round trips
6 calls × 3 = 18 round trips × 180 ms ≈ 3.2 s, plus 240 ms of server work.
Why: Round trips × RTT is the bill. Count before touching code.
- 2Pay for the hello once
One HTTPS connection kept open (keep-alive, HTTP/2) for all six calls.
Why: Handshakes are 12 of the 18 round trips; reuse removes 10 of them.
- 3Stop waiting in line
Login first; the five calls after it don't depend on each other, so send them in parallel on the same connection.
Why: Five in a row cost 5 round trips; together, about 1.
- 4Handle the loss, then measure
Turn on HTTP/3 at the CDN for photos (2% loss). New median: 580 ms + 220 ms ≈ 0.8 s.
Why: QUIC isolates streams on lossy links. The first call's handshake is now the biggest slice: moving closer is a separate decision.
In real life
3 minZoom: live calls over UDP
- Publicly documents that media prefers UDP, falling back to TCP/TLS on port 443 when UDP is blocked
- A late frame is skipped, not waited for
- Needs its own loss handling and a fallback path
Fits live media, where waiting is worse than losing a frame.
Netflix: recorded video over HTTP and TCP
- Streams video files over HTTP from its caching servers
- The player buffers seconds ahead, so a resent packet arrives in time
- Every byte must be correct
Fits recorded content, where a buffer hides delay.
What goes wrong
3 minWrites are split and merged however TCP likes.
Frame messages (newline or length prefix), or use HTTP, gRPC or WebSocket.
Each pays 2–3 round trips of handshakes.
Keep-alive, connection pools, HTTP/2 or HTTP/3.
You'll need reliability anyway, and hand-rolled resends are slower and buggier.
TCP or QUIC for anything that must arrive complete; UDP where late data is worthless.
Trade-offs
3 min| Transport | Fits | You pay |
|---|---|---|
| TCP | APIs, databases, files: complete and in order | Handshakes; head-of-line blocking |
| UDP | Live media, games, telemetry, DNS | No reliability unless you build it; blocked on some networks |
| QUIC / HTTP/3 | Web and mobile on lossy networks | More CPU; needs a fallback; harder to inspect |
- Karim · CTOHTTP/3 is the future. Run it end to end, phone to Frankfurt, this sprint.
- Lina · Senior engineer, your mentorOur load balancer and tools aren't ready, and some hotel Wi-Fi blocks UDP. Connection reuse gets us 3.4 s to 0.8 s now.
- Karim · CTOAnd then we do the migration twice.
Your job as tech lead
4 minClaude Code can write the code for this. You still own the decisions, and you have to know what good looks like to accept or reject its work.
- The transport per traffic type: bookings over HTTPS, live voice over UDP, photos over HTTP/3 at the CDN. "UDP is faster" is never a reason alone.
- The latency budget from far away, counted in round trips, before anyone moves servers.
- Whether internal services use a real protocol (HTTP, gRPC). Raw TCP means you own message framing.
The StayHub app takes 3.4 s to open in Jakarta: 6 calls in a row, each on a new HTTPS connection to Frankfurt (180 ms round trip, 40 ms server). Don't move servers. First show me where the app creates its HTTP client and count round trips per call. Then propose one shared keep-alive HTTP/2 client, login first, the other five calls in parallel. Don't change the API. Measure start time on a simulated 180 ms, 2% loss network, before and after.
- One shared HTTP client instance, not a new one per call.
- Login first, then the five other calls started and awaited together.
- The measured median drops from about 3.4 s to about 0.8 s on the simulated link.
- A custom UDP protocol for the API.
- JSON over a raw TCP socket with no delimiter or length prefix.
- 0-RTT enabled for requests that change data, which can be replayed.
Check yourself
2 minNext episode: on Saturday at 00:00 UTC, every guest's browser starts showing "Your connection is not private" on stayhub.com.
Mark it complete to update your skill map.
Go deeper (optional)You don't need this to understand the lesson. Open it when a project needs the details: Technical deep dive, Interactive simulation, Visual diagram, Analogy, Check your understanding, Practice ladder, Explain it yourself.
Technical deep dive
Lina fixes app start for Jakarta
The app takes 3.4 s to open in Jakarta. The API is in Frankfurt (RTT 180 ms) and answers in 40 ms. Cut the start time without moving any servers.
- 1Count the round trips
6 calls × (TCP 1 + TLS 1 + request 1) = 18 round trips × 180 ms ≈ 3.2 s, plus 240 ms of server work.
Why: Round trips × RTT is the bill. Counting them tells you what's worth fixing before touching any code.
- 2Pay for the hello once
Keep one HTTPS connection open (keep-alive, HTTP/2) and send all six calls over it.
Why: Handshakes are 12 of the 18 round trips. Reusing the connection removes 10 of them.
- 3Stop waiting in line
The five calls after login don't depend on each other: send them in parallel on the same connection.
Why: Five calls in a row cost 5 round trips; together, about 1. HTTP/2 multiplexes them on one connection.
- 4Check the loss
Indonesian mobile data loses about 2% of packets. On HTTP/2, one loss stalls every photo on the connection. Turn on HTTP/3 at the CDN for photos.
Why: On lossy links, TCP's single ordered stream turns one lost packet into a pause for everything. QUIC isolates streams.
- 5Measure again
New median from Jakarta: first call 580 ms + the other five together 220 ms ≈ 0.8 s to open.
Why: The biggest slice is now the first call's handshakes. Moving that closer to Jakarta is a separate, bigger decision.
Counting round trips before the first byte of an answer on a new connection (ignoring DNS and server time): TCP + TLS 1.3 = 3 RTT; TCP + TLS 1.2 = up to 4 RTT; QUIC (HTTP/3) = 2 RTT, or the request goes in the first flight with 0-RTT on a repeat visit (only safe for requests that can be repeated, because 0-RTT data can be replayed); a reused connection = 1 RTT.
import net from "node:net";
import dgram from "node:dgram";
// TCP: a stream. Writes may be split or merged; frame messages yourself (newline or length prefix).
const sock = net.connect({ host: "holds.internal", port: 7000 }, () => {
sock.write(JSON.stringify({ type: "hold", room: 204 }) + "\n");
});
sock.setNoDelay(true); // send small writes at once (request/response protocols)
// UDP: datagrams. Each send is one message, delivered whole or not at all, maybe out of order.
const udp = dgram.createSocket("udp4");
udp.send(Buffer.from(JSON.stringify({ shuttle: 7, lat: -6.2, lng: 106.8, t: Date.now() })), 9000, "telemetry.internal");
// No ACK, no retry: the next position in 1 s makes a lost one irrelevant.Interactive simulation
Visual diagram
One phone, three kinds of traffic. Step through what each transport does.
Analogy
- Sending mailA phone call first: "ready?" "ready!"Sending dataThe TCP handshake
- Sending mailNumbered registered letters, each signed forSending dataSequence numbers and confirmations
- Sending mailLetter 5 lost, letters 6 to 20 waitSending dataHead-of-line blocking
- Sending mailPostcards: cheap, quick, sometimes lostSending dataUDP datagrams
- Sending mailSeveral vans, each with its own parcelsSending dataQUIC's independent streams
Where the analogy breaks: real letters take days. Network round trips take milliseconds, but a page may need dozens of them, so they add up fast.
Check your understanding
Practice ladder
Count the round trips
Estimation Warm-upA guest in Jakarta (RTT to Frankfurt 180 ms, server work 40 ms) makes one API call. For each case, count the round trips before the first byte of the answer, and the time:
- New connection, TCP + TLS 1.3
- New connection, TCP + TLS 1.2
- New connection, QUIC (HTTP/3), first visit
- Reused connection (any protocol)
- QUIC with 0-RTT on a repeat visit
Then say which one the fixed app uses for its 2nd to 6th calls.
Frame the Holds messages
Code PracticeLina's fixed receiver for the Holds socket has a gap. The sender now ends every JSON message with "\n". Fill in the gap so that split and glued messages are both handled, and explain what buffer is for.
let buffer = "";
sock.on("data", (chunk) => {
buffer += chunk.toString("utf8");
// gap: ___
});Live shuttle positions
Design PracticeHotels in Bali want guests to see the airport shuttle moving on a map in the StayHub app. Each shuttle sends its GPS position every second; up to 3,000 guests may watch at once.
- A position is useless once a newer one exists. Missing one is fine.
- Guests are on mobile data (about 2% loss) or hotel Wi-Fi, some of which blocks UDP.
- The app is a mobile app and a website (browsers can't open raw UDP sockets).
Choose how positions travel from the shuttle to StayHub and from StayHub to guests. Justify each, and say what you'd measure to know whether to change it.
Turn on 0-RTT?
Decision StretchThe CDN now serves StayHub over HTTP/3 and offers 0-RTT resumption: on a repeat visit, the first request travels with the hello, saving one round trip (180 ms from Jakarta). Its documentation warns that 0-RTT data can be replayed by an attacker who records it.
Karim: "Free speed. Turn it on for everything."
Requests that would benefit: GET /hotels/..., GET /search, but also POST /bookings/:id/pay and POST /reviews.
Write a short decision: "We chose X over Y because…, accepting the risk of…".
Explain it yourself
Explain to a product manager why the booking API uses TCP, the video call uses UDP, and the photos are moving to HTTP/3.