TCP vs UDP
Both protocols ride on top of IP, which delivers individual packets best-effort with no promises. The difference is what each layer adds on top: TCP stamps every byte with a sequence number and makes the receiver return acknowledgements, so the sender can detect gaps and retransmit — turning an unreliable packet network into an ordered, lossless byte stream. UDP adds almost nothing — just ports and a checksum — and hands each datagram to the application exactly as it arrives, or not at all. Everything else (handshake, ordering, congestion control, head-of-line blocking) follows from that one choice: track-and-retransmit versus fire-and-forget.
How TCP builds a reliable stream
TCP is not magic on the wire — the wire is still lossy IP. Reliability is a bookkeeping protocol layered on top:
- Sequence numbers — every byte has a number. A segment carrying bytes 1001–1100 has sequence number 1001. This lets the receiver detect gaps and reorder segments that arrive out of order.
- Cumulative ACKs — the receiver replies with the next byte it expects. "ACK 1101" means "I have everything up to and including byte 1100; send 1101 next."
- Retransmission — if an ACK does not come back before a timer (the RTO) expires, or if three duplicate ACKs arrive — the sender sees the same ACK number four times in total, the original plus three duplicates (→ fast retransmit) — it resends the missing segment.
- Sliding window / flow control — the receiver advertises how much buffer it has (the window); the sender may have that many unacknowledged bytes in flight at once, no more.
- Congestion control — a separate congestion window (cwnd) grows on success and shrinks on loss, so TCP backs off when the network is overloaded.
None of this exists until a connection is established, which is what the three-way handshake is for.
Note the handshake costs a full round trip before any data moves — the client cannot send its request until the third packet. On a link with 50 ms one-way latency, that is 100 ms of pure setup, plus another round trip for TLS on top if this is HTTPS. This is exactly the tax UDP-based protocols try to avoid.
Worked trace: one lost segment, and how TCP recovers
The client sends a 600-byte body split into six 100-byte segments (bytes 1001–1600). Segment 3 (bytes 1201–1300) is dropped by a congested router. Watch how sequence numbers and duplicate ACKs recover it — each duplicate ACK below is generated by a named arriving segment, and note what happens to segments 4–6 that did arrive.
| Step | Sender → (seq, bytes) | Receiver has | ← ACK (next expected) | What it means |
|---|---|---|---|---|
| 1 | seq 1001 (1001–1100) | 1001–1100 | ack 1101 | in order, delivered to app |
| 2 | seq 1101 (1101–1200) | …–1200 | ack 1201 | in order, delivered to app |
| 3 | seq 1201 (1201–1300) | LOST | — | never arrives |
| 4 | seq 1301 (1301–1400) | buffered, gap at 1201 | ack 1201 (dup #1) | "still need 1201" — held, NOT delivered |
| 5 | seq 1401 (1401–1500) | buffered, gap at 1201 | ack 1201 (dup #2) | "still need 1201" — held, NOT delivered |
| 6 | seq 1501 (1501–1600) | buffered, gap at 1201 | ack 1201 (dup #3) | "still need 1201" — 3rd dup ACK: triggers fast retransmit |
| 7 | seq 1201 (retransmit) | gap filled: 1001–1600 complete | ack 1601 | 1201–1600 all delivered to app at once |
The key observation is steps 4–6: segments 4, 5, and 6 arrived intact but the receiver could not hand them to the application, because delivering byte 1301 before byte 1201 would violate ordering. They sat in a kernel buffer until the retransmit of segment 3 landed. That stall is head-of-line (HOL) blocking: one lost packet freezes every byte behind it. UDP has no such rule — a lost datagram is simply gone, and the next one is delivered immediately.
One more thing the trace shows by construction: a duplicate ACK is only generated when an out-of-order segment arrives, so fast retransmit needs at least three segments to land after the lost one. Had segment 3 been among the last segments of the transfer — fewer than three following it — no third dup ACK would ever arrive and the sender would sit waiting for the RTO timer. Tail losses are recovered by timeout, which is why they dominate tail latency, and why mechanisms like RACK-TLP (RFC 8985) exist to probe for them sooner.
What SACK changes
Now rerun the same trace with two losses — segment 3 (seq 1201) and segment 5 (seq 1401). With cumulative ACKs alone, every dup ACK still just says "ack 1201"; after the sender retransmits 1201, the next ACK jumps only to 1401 — revealing the second hole one full RTT later, so recovery costs 2 RTTs (one extra RTT per additional hole). With SACK (Selective Acknowledgment, RFC 2018), each dup ACK also carries a map of what the receiver does hold — e.g. ack 1201, SACK 1301–1400, then ack 1201, SACK 1301–1400, 1501–1600 — so the sender sees both holes immediately and retransmits 1201 and 1401 in the first recovery RTT. SACK is negotiated in the SYN and is on by default in every mainstream OS; the dup-ACK counting above still triggers recovery, but SACK tells the sender exactly which holes to fill.
What UDP actually is
A UDP datagram is an 8-byte header (source port, destination port, length, checksum) wrapped around your payload, dropped onto IP, and forgotten. There is no connection state, no sequence number, no ACK, no window, no congestion control. If a datagram is lost, duplicated, or reordered, UDP will not tell you and will not fix it. That sounds like a weakness, and for file transfer it is — but it is precisely what you want when stale data is worthless: in a voice call, a 40 ms audio frame that arrives 300 ms late is useless, so retransmitting it (TCP's instinct) only adds jitter. UDP lets you skip it and play the next frame. Modern UDP applications typically build their own lightweight reliability on top — QUIC, RTP, and game netcode all do — but they get to choose exactly which packets are worth resending, which the kernel's one-size-fits-all TCP cannot.
| TCP | UDP | |
|---|---|---|
| Header size | 20 bytes (min) | 8 bytes |
| Connection | 3-way handshake first | none — send immediately |
| Ordering | guaranteed (seq numbers) | none |
| Loss recovery | ACK + retransmit | none (app's job) |
| Congestion control | yes (backs off on loss) | none |
| HOL blocking | yes — one loss stalls the stream | no |
| Multicast/broadcast | no (point-to-point only) | yes |
Pitfalls
- Assuming one
send()= onerecv()on TCP. TCP is a byte stream, not a message protocol. Two 100-byte sends can arrive as one 200-byte read, or split across two reads. You must frame messages yourself (length prefix or delimiter). UDP does preserve message boundaries — onesendtois onerecvfrom— which is a real reason to pick it. - UDP fragmentation black holes. A UDP payload larger than the path MTU (~1500 bytes, less with tunnels) gets fragmented by IP; if any fragment is lost, the whole datagram is dropped, and some middleboxes silently discard fragments entirely. Keep UDP payloads under ~1200 bytes to stay safe.
- Head-of-line blocking under HTTP/2-over-TCP. HTTP/2 multiplexes many streams over one TCP connection, but one lost packet stalls all of them (see the trace above). This is the specific problem QUIC/HTTP/3 was built to solve by moving reliability into UDP with per-stream ordering.
- TIME_WAIT exhaustion. A server that opens and closes many short TCP connections leaves thousands of sockets in TIME_WAIT (holding the 4-tuple for ~60 s), which can exhaust ephemeral ports. Connection reuse / keep-alive, not UDP, is the usual fix.
- No congestion control is antisocial. A high-rate UDP sender with no backoff can starve every TCP flow sharing the link (TCP politely backs off; UDP doesn't). If you build on UDP at scale, you must implement congestion control — this is a large part of what QUIC does.
- Firewalls and NAT favor TCP. Many corporate firewalls block or rate-limit UDP, and NAT mappings for UDP time out fast (needing keepalives). This is why WebRTC and QUIC ship TCP/TLS fallbacks.
- Health-checking a UDP service over TCP lies. A load balancer or orchestrator that probes a TCP port to decide "healthy" will report a UDP-only service (DNS, a game server, a QUIC endpoint) as up or down based on a port that the app never uses — a false signal in both directions. UDP has no connection to accept, so a TCP connect probe proves nothing; you need an application-level UDP probe (a real query that expects a reply) instead.
When to use which — and the trade-off
Ask one question first: is a late byte still valuable, or is it garbage?
- Choose TCP when correctness and order matter and the data is still useful whenever it arrives: HTTP/HTTPS page loads and APIs, database connections, file transfer (FTP/SFTP), email (SMTP/IMAP), anything where a missing byte corrupts the result. You gain guaranteed, ordered, congestion-friendly delivery; you pay one setup round trip, per-packet ACK overhead, and HOL blocking on loss.
- Choose UDP when freshness beats completeness, or you need one-to-many: live audio/video and VoIP (RTP), online-game state updates, DNS queries (one small request/reply, cheaper than a handshake), NTP, service-discovery multicast, and metrics like StatsD where dropping a sample is harmless. You gain minimal latency, message boundaries, and multicast; you give up ordering, loss recovery, and congestion control — and must rebuild whatever subset you need yourself.
The modern alternative — QUIC (HTTP/3). QUIC runs on UDP but adds back TCP-grade reliability, encryption, and congestion control in user space, with two decisive wins: connection setup folds the transport and TLS handshakes into a single round trip (0-RTT on resumption), and each stream has independent ordering, so one lost packet stalls only its own stream, not the whole connection. Prefer QUIC over raw TCP when you multiplex many streams over one connection and tail latency matters (web at scale, mobile with frequent network changes); prefer plain TCP when you want kernel-level maturity, simple tooling, and firewalls you don't control; prefer raw UDP only when you need the absolute thinnest layer or multicast and are prepared to own reliability yourself. In short: UDP is the raw material, TCP is the general-purpose reliable stream, QUIC is the tailored reliable stream that fixes TCP's head-of-line and handshake costs.
Takeaways
- Reliability is bookkeeping on top of lossy IP: sequence numbers + cumulative ACKs + retransmit. That mechanism is the whole story — ordering, flow control, and HOL blocking all fall out of it.
- TCP's ordering guarantee causes head-of-line blocking: intact packets wait behind one lost one. UDP has no such rule, which is a feature for real-time media.
- Decide by asking whether a late byte is still worth having. Yes → TCP (or QUIC); no → UDP.
- UDP is not "TCP but faster" — it's a blank slate. At scale you'll re-implement congestion control and partial reliability, which is exactly what QUIC already did.
Re-authored and deepened for this guide. Mechanism, sequence-number/ACK semantics, and fast-retransmit behavior follow RFC 9293 (TCP) and RFC 768 (UDP); congestion-control and loss-recovery details from RFC 5681. Head-of-line blocking and the QUIC/HTTP-3 comparison draw on RFC 9000 (QUIC) and RFC 9114 (HTTP/3). Cross-checked against W. Richard Stevens, TCP/IP Illustrated, Vol. 1, and Kurose & Ross, Computer Networking: A Top-Down Approach.
🤖 Don't fully get this? Learn it with Claude
Stuck on TCP vs UDP? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **TCP vs UDP** (System Design) and want to truly understand it. Explain TCP vs UDP from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **TCP vs UDP** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **TCP vs UDP** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **TCP vs UDP** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.