Course contents

TCP vs UDP

TCP vs UDP for system design interviews: the three-way handshake, sequence numbers and retransmission, flow vs congestion control, head-of-line blocking, TIME_WAIT, UDP's 8-byte header, QUIC and HTTP/3, connection pooling in round trips, and which transport to pick for video calls, games, DNS, chat and payments.

FreeMust know for interviewsLesson15 min readBeginner → SeniorReviewed Oct 2026
On this page
  1. Picture it first: a TCP connection's whole life
  2. What TCP gives you, and what each promise costs
  3. UDP: 8 bytes and a promise of nothing
  4. QUIC: UDP plus reliability in user space
  5. Connection reuse: the cheapest latency win
  6. When to pick each
  7. In Java, briefly
  8. Trade-offs and failure modes
  9. How it shows up in design problems
  10. Follow-up questions
  11. How to say it in the interview

🚚 What are TCP and UDP?

TCP and UDP are the two transport protocols almost every app sits on. TCP gives you a connection and a reliable, ordered stream of bytes: it numbers everything, waits for acknowledgements and resends what got lost. UDP gives you single packets (datagrams) with a port number and a checksum, and nothing else: no connection, no resends, no ordering. TCP is a tracked courier that gets a signature for every parcel; UDP is dropping postcards in a box.

For this lesson, we'll cover what TCP actually does for you (handshake, acknowledgements, ordering, flow and congestion control, teardown), what UDP leaves out, why QUIC rebuilt TCP's job on top of UDP, and how to pick one in a design. Interviewers ask it because "TCP is reliable, UDP is fast" is only half an answer; the interesting part is what reliability costs and when an app is better off without it.

Below the line (out of scope)

  • Where these sit in the stack: The OSI model
  • Addresses and ports below them: IP addresses
  • What runs on top of TCP and QUIC: HTTP and HTTPS
  • The checksum both protocols carry: Checksums

The 30-second answer

TCP is connection-oriented: a three-way handshake costs one round trip, then it delivers a reliable, ordered byte stream using sequence numbers, acknowledgements and retransmission, with flow control so the receiver isn't flooded and congestion control so the network isn't. The price is setup latency and head-of-line blocking: one lost packet stalls everything behind it. UDP has an 8-byte header with ports, length and a checksum, and no delivery or ordering guarantees, so the app decides what to rebuild. I pick TCP for anything where every byte matters (APIs, payments, chat, file transfer) and UDP where late data is useless (voice, video, game state) or the exchange is one tiny question (DNS). QUIC, which carries HTTP/3, is the middle path: TCP-style reliability and TLS 1.3 built in user space over UDP, with independent streams and a 1-RTT setup.

What you’ll learn

  • Draw the three-way handshake and teardown, and explain why the side that closes first sits in TIME_WAIT
  • Explain how TCP gets reliability and ordering from sequence numbers, ACKs and retransmission, and what head-of-line blocking costs
  • Tell flow control (protects the receiver) from congestion control (protects the network) in one sentence
  • Say what UDP's 8-byte header gives you and what apps rebuild on top of it
  • Describe QUIC as reliability and TLS 1.3 in user space over UDP, and when HTTP/3 helps
  • Cost a fresh vs pooled HTTPS connection in round trips and pick a transport per use case

01Picture it first: a TCP connection's whole life

The fastest way to understand TCP is to watch one connection from start to finish. Every promise TCP makes shows up as a message in this picture.

Fig. 1

A TCP connection from SYN to TIME_WAIT

A TCP connection from SYN to TIME_WAITA sequence diagram between a client and a server. The client sends SYN, the server answers SYN-ACK, the client sends ACK: one round trip before any data. Data segments carry sequence numbers and the receiver acknowledges them; a lost segment is retransmitted. To close, each side sends FIN and gets an ACK, and the side that closed first waits in TIME_WAIT.1SYN seq=x2SYN-ACK seq=y, ack=x+13ACK ack=y+1 (request can ride along)4data seq=x+1, 1,000 bytes5data seq=x+1001 (lost)6ACK ack=x+1001 (send me 1001 next)7retransmit seq=x+10018FIN (I am done sending)9ACK, then its own FIN10ACKClientServerTIME_WAIT on the client2 x MSL in the spec, 60 s on LinuxServer socket closedthe closer pays TIME_WAIT, not the peerhandshake: 1 RTT before the first byte of data
Reliability is bookkeeping. Sequence numbers and ACKs let the sender spot the gap and resend only what's missing, and the side that closes first is the one stuck in TIME_WAIT.

Notice four things:

  • The handshake is a round trip of pure overhead. SYN, SYN-ACK, ACK agrees on starting sequence numbers before any data moves. The client's request can ride on the final ACK, so setup costs one RTT, not one and a half.
  • ACKs are cumulative. "ack=x+1001" means "I have everything up to byte 1000, send 1001 next". The sender doesn't need a receipt per segment.
  • Loss is repaired by the sender. It notices either because a timer expires (RFC 6298 starts it at 1 second before any RTT is measured) or because it gets three duplicate ACKs for the same byte, which triggers a fast retransmit (RFC 5681).
  • Closing is a two-way goodbye. Each direction sends its own FIN, so a connection can be half-closed for a moment.

02What TCP gives you, and what each promise costs

Promise Mechanism What it costs
Connection Three-way handshake, state on both ends 1 RTT before data; memory per connection
Reliability Sequence numbers, ACKs, retransmission Extra round trips on loss; latency spikes
Ordering Receiver buffers out-of-order bytes until the gap fills Head-of-line blocking
Flow control Receiver advertises a window: "I have room for N bytes" A slow reader throttles a fast sender
Congestion control Sender's congestion window grows until loss or delay says stop New connections start slow
Integrity 16-bit checksum over header and data Catches some corruption, not all (see Checksums)

Flow control vs congestion control

The one-line split I use: flow control protects the receiver, congestion control protects the network.

  • Flow control. Every ACK carries a receive window: the free space in the receiver's buffer. If your consumer reads slowly, the window shrinks to zero and the sender pauses. That's TCP doing backpressure for you, the same idea as backpressure between services.
  • Congestion control. The sender also keeps its own congestion window, a guess at what the path can carry. It starts small, roughly doubles every round trip (slow start), then grows cautiously and cuts back when it sees loss. RFC 6928 raised the starting window to 10 segments, about 14 KB with typical 1,460-byte segments, which is why a fresh connection can't send a 1 MB response in its first round trip.

The sender may have whichever window is smaller in flight. In an interview, the point is that a brand-new connection is slow on purpose, which is a big reason to reuse connections.

Head-of-line blocking

TCP hands bytes to the application strictly in order. If segment 5 is lost and 6 to 20 arrive fine, the app sees nothing past 4 until 5 is resent. For a protocol that multiplexes many requests over one connection, like HTTP/2, one lost packet stalls every request, which is the main problem QUIC was built to fix.

Fig. 2

One lost packet: TCP stalls every stream, QUIC stalls one

One lost packet: TCP stalls every stream, QUIC stalls oneTwo panels. On the left, three HTTP/2 streams share one TCP byte stream; a packet of stream B is lost, so the kernel holds back everything after it, including data for stream C. On the right, QUIC carries the same streams over UDP; only stream B waits for the retransmission while A and C are delivered.HTTP/2 over TCPHTTP/3 over QUIC (UDP)A1B1B2C1A1B1waitheldA1B1B2C1A1B1waitC1C1 arrived, but sits in the bufferTCP hands over bytes strictly in order,so stream C stalls behind B2Only stream B waitsA and C keep flowing; B resumeswhen its retransmission landsArrives on the wire, in this orderWireHanded to the applicationAppArrives on the wire, in this orderWireHanded to the applicationAppB2 lostB2 lost
TCP orders the whole connection; QUIC orders each stream. Same loss, but on QUIC only the stream that lost data waits.

Teardown and TIME_WAIT

After the last ACK, the side that closed first parks the connection in TIME_WAIT for twice the maximum segment lifetime. RFC 9293 takes the MSL as 2 minutes, so 4 minutes on paper; Linux hard-codes 60 seconds. The wait makes sure stray segments from the old connection die before a new one reuses the same address and port pair.

Here's why that matters in a design. A client making many short connections to one destination burns an ephemeral port per connection. Linux's default ephemeral range is 32768 to 60999, about 28,000 ports, so at 60 seconds of TIME_WAIT that's roughly 470 new connections a second to one destination IP and port before you run out. The fix is almost never kernel tuning; it's connection pooling.

03UDP: 8 bytes and a promise of nothing

UDP (RFC 768) adds four 16-bit fields to an IP packet and stops there:

 0               16              31
+---------------+---------------+
|  source port  |  dest port    |
+---------------+---------------+
|    length     |   checksum    |
+---------------+---------------+
|  data (one datagram) ...      |

No handshake, no ACKs, no ordering, no congestion control, no connection state. Each datagram arrives once, never, or (rarely) twice or out of order. The checksum is optional over IPv4 and mandatory over IPv6 (RFC 8200). Apps that pick UDP rebuild only the parts they need:

Need What UDP apps typically add
Match a reply to a request An ID in the payload (DNS uses a 16-bit query ID)
Detect loss Sequence numbers per packet (RTP for media does this)
Recover loss Selective resend, or forward error correction, or "skip it"
Ordering Reorder buffer with a deadline, then play what you have
Don't flood the network Rate control based on measured loss or delay
Security DTLS, or QUIC's built-in TLS 1.3

The pattern: a voice call wants loss detection (to conceal a gap) but not recovery, because a packet that arrives 300 ms late is worse than no packet. TCP can't give you that; it will always wait for the resend.

04QUIC: UDP plus reliability in user space

QUIC (RFC 9000) is a full transport, with streams, ACKs, retransmission and congestion control, built on top of UDP and shipped inside the application or library instead of the OS kernel. HTTP/3 (RFC 9114) runs on it. Three things matter for interviews:

  1. Independent streams. Loss on one stream doesn't block the others, which removes TCP's head-of-line blocking.
  2. Faster setup. QUIC does the transport and TLS 1.3 handshakes together in one round trip, and can send data in the first flight (0-RTT) when resuming. Encryption isn't optional.
  3. Connection IDs, not addresses. A QUIC connection is identified by an ID, so a phone moving from Wi-Fi to cellular can keep the connection instead of reconnecting (RFC 9000 calls this connection migration).

Why not fix TCP instead? TCP lives in every OS kernel and middleboxes inspect it, so changing it takes years. UDP gets through almost everywhere, and logic in user space ships with the next app release. The figure lines the two stacks up.

Fig. 3

Same job, two stacks: what each layer costs in round trips

Same job, two stacks: what each layer costs in round tripsTwo stacks side by side. Left: HTTP/2 on TLS on TCP on IP, where a new connection spends one round trip on the TCP handshake and one on the TLS 1.3 handshake before the request. Right: HTTP/3 on QUIC on UDP on IP, where QUIC folds transport and TLS 1.3 setup into one round trip, or zero on resumption, and does reliability in user space.HTTP/2streams, HPACK header compressionTLS 1.3+1 RTT on a new connectionTCP (in the kernel)+1 RTT handshake; reliability,ordering, congestion controlIPbest-effort packetsHTTP/3streams map onto QUIC streams, QPACKQUIC (in user space)transport + TLS 1.3 in 1 RTT,0 RTT on resumption;loss recovery per stream;connection IDs survive Wi-Fi to 5GUDP over IP8-byte header: ports, length, checksumThen one more round trip for the request and response itself, on either stack.Classic web stackHTTP/3 stacknew HTTPS connection: 2 RTT before the requestnew: 1 RTT; resumed: 0 RTT
QUIC folds the transport and TLS handshakes into one round trip. It's TCP's job, moved out of the kernel and onto UDP.
In the real world

Uber's rider and driver apps run on lossy mobile networks in hundreds of cities. When their engineers traced production TCP connections from India, they saw tail RTTs close to six times the median and TCP retransmitting about 3.5% of packets, up to 7% in crowded places like railway stations. After moving the apps' HTTPS traffic to QUIC, they reported 10 to 30% lower tail latency at scale. The lesson I take from it: on clean data-centre links TCP is fine, but on the last mile to a phone, head-of-line blocking and kernel-bound loss recovery show up directly in p99.

05Connection reuse: the cheapest latency win

Every new HTTPS connection over TCP pays a TCP handshake, then a TLS handshake, then the request itself. Reusing a connection (HTTP keep-alive, a client-side connection pool, HTTP/2 multiplexing) skips the first two entirely.

Try it yourself first

Before reading on, work this out. Your service in Mumbai calls a payment provider in Virginia, about 200 ms round trip. Each call opens a fresh HTTPS connection with TLS 1.3. How long before the first byte of the response arrives? What if the connection is pooled? And what if you made 5 calls in a row?

Here's how I'd do it out loud (ignoring server processing time):

Step Fresh connection Pooled connection
TCP handshake 1 RTT = 200 ms 0
TLS 1.3 handshake 1 RTT = 200 ms 0
Request and response 1 RTT = 200 ms 1 RTT = 200 ms
One call about 600 ms about 200 ms
Five sequential calls about 3 s about 1 s

With TLS 1.2 it's one more round trip per new connection. Notice which number drives the design: two thirds of the fresh-connection time is setup. Pooling, keep-alive and putting a TLS-terminating proxy close to the caller all attack that, which is also why CDNs terminate TLS near users (see CDN).

Performance angle

Slow start adds to the bill on a cold connection. With an initial window of 10 segments of 1,460 bytes, the server can send about 14.6 KB in the first round trip, then roughly 29 KB, then 58 KB as the window doubles. So a 100 KB response needs about 3 round trips on a fresh connection, versus 1 on a warm one whose window has already grown (our own rough arithmetic, ignoring loss). Inside one data centre the RTT is tiny (see Numbers to know), so I care most about pooling on cross-region and internet-facing calls.

06When to pick each

This is the table I'd sketch.

Use case Pick Why
Payments, orders, any API TCP (HTTP/1.1, HTTP/2) or QUIC Every byte must arrive, in order; add idempotency for retries
Chat messages TCP (WebSockets) Order and delivery matter more than 50 ms; see real-time connections
Video and voice calls UDP (WebRTC media over RTP) A late frame is useless; conceal the gap and keep going
Multiplayer game state UDP with custom reliability Latest position wins; resend only what must arrive (a purchase, a kill)
DNS lookup UDP first, TCP for large answers One small question, one answer; no handshake worth paying for (DNS)
Metrics (StatsD-style) UDP Losing 0.1% of counter increments is fine; blocking the app isn't
Video on demand TCP or QUIC (HTTP) A buffer of several seconds hides retransmissions
Big file transfer TCP or QUIC Reliability and congestion control are exactly what you want
Where candidates slip

Many candidates say "UDP because it's faster" for anything latency-sensitive, then quietly need every message to arrive. Choosing UDP means you now own loss detection, retransmission, ordering and congestion control, and building them badly is how you end up slower than TCP and unfair to other traffic. Pick UDP only when you can name what you'll skip (usually retransmitting stale data). If you need reliability without head-of-line blocking, the honest answer is often QUIC, not raw UDP.

07In Java, briefly

You rarely touch raw sockets in a design interview, but the API shape shows the difference.

// TCP: connect (handshake), then a reliable ordered stream
try (Socket tcp = new Socket()) {
    tcp.connect(new InetSocketAddress("orders.internal", 8080), 2_000); // connect timeout, ms
    tcp.setSoTimeout(3_000);                                            // read timeout, ms
    tcp.getOutputStream().write(request);
}

// UDP: one datagram, no connection, no delivery report
try (DatagramSocket udp = new DatagramSocket()) {
    byte[] m = "checkout.latency:42|ms".getBytes(StandardCharsets.UTF_8);
    udp.send(new DatagramPacket(m, m.length, new InetSocketAddress("metrics.internal", 8125)));
}

Notice the UDP send returns happily even if nobody is listening. Real services use a pooled HTTP client instead; HTTP and HTTPS shows one.

08Trade-offs and failure modes

Symptom Usually caused by What to do
p99 latency spikes on mobile Loss plus TCP retransmission and head-of-line blocking HTTP/3 (QUIC) at the edge
"Cannot assign requested address" on a busy client Ephemeral ports stuck in TIME_WAIT from short connections Pool and reuse connections
First request on every call is slow New connection: handshakes plus slow start Keep-alive, warm pools
Hangs forever on a dead peer No read timeout; Linux TCP keepalive waits 2 hours by default Always set connect and read timeouts (fault tolerance)
HTTP/3 fails on some networks UDP blocked or rate-limited by a firewall Keep a TCP fallback; clients learn HTTP/3 via Alt-Svc

09How it shows up in design problems

  • Design Zoom: media over UDP with loss concealment, signalling and chat over TCP. Saying "two transports for two jobs" is a strong signal.
  • Design WhatsApp and Slack / Discord: long-lived TCP connections (WebSockets) per device; the deep dive is connection count per server and reconnect storms.
  • Design a payment system: TCP plus TLS, pooled connections to the provider, timeouts and idempotent retries.
  • Design a rate limiter: the limiter sits on the hot path, so pooled connections to its counter store matter more than you'd think.

At this point, you should ask your interviewer whether clients are on mobile networks. If they are, the transport choice (and HTTP/3) becomes part of your latency story.

10Follow-up questions

What if your service starts failing with thousands of sockets in TIME_WAIT?

That's a client opening and closing short connections to the same destination, and running out of ephemeral ports (about 28,000 by default on Linux, each held for 60 seconds). I'd fix the cause: a connection pool with keep-alive so a few hundred long-lived connections carry the traffic. Kernel options that reuse TIME_WAIT sockets exist, but they treat the symptom.

What if a user's phone switches from Wi-Fi to 5G in the middle of an upload?

Over TCP the connection is tied to the old IP address and port, so it breaks and the app must reconnect, redo TCP and TLS, and resume the upload, ideally from the last acknowledged chunk. Over QUIC the connection ID survives the address change, so the transfer continues. Either way, I'd design uploads as resumable chunks so a dropped connection costs one chunk, not the whole file.

What if you need reliable messages for a game but can't afford head-of-line blocking?

Split traffic by what it needs. Position updates go unreliable and unordered, since only the latest one matters. Events that must arrive (a purchase, a match result) go on a reliable channel: either your own ACK and resend on UDP, or separate QUIC streams so one lost event doesn't freeze movement updates.

What if the interviewer pushes: "Why not run HTTP/3 between your internal services too?"

Inside a data centre, loss is low, RTTs are about a millisecond and connections are long-lived and pooled, so QUIC's wins (faster setup, no cross-stream blocking on lossy links, migration) mostly disappear. Meanwhile TCP has mature kernel offloads, load balancer support and debugging tools. I'd use HTTP/3 at the internet edge where users are, and keep HTTP/2 or gRPC over TCP inside unless measurements say otherwise.

11How to say it in the interview

When I ask "TCP or UDP?", I'm listening for three things: that you know what TCP's reliability costs (round trips and head-of-line blocking), that you can say what a UDP app has to rebuild, and that you pick per use case rather than per system.

In the interview

"TCP gives me a reliable, ordered byte stream: a one-round-trip handshake, sequence numbers and ACKs with retransmission, flow control so the receiver isn't overrun, and congestion control so the network isn't. The cost is setup latency and head-of-line blocking, where one lost packet holds up everything behind it. UDP is an 8-byte header with ports and a checksum and nothing else, so the application decides what to rebuild. For our API calls, payments and chat I'd use TCP, with pooled keep-alive connections, because a fresh HTTPS connection costs two extra round trips before the request. For the video call media I'd use UDP, because a late frame is worthless and I'd rather conceal the gap than wait for a resend. If mobile tail latency matters, I'd serve HTTP/3 at the edge: QUIC gives TCP-style reliability per stream over UDP, a combined 1-RTT handshake with TLS 1.3, and connections that survive a switch from Wi-Fi to cellular, while keeping a TCP fallback for networks that block UDP."

On the whiteboard

  • TCP: handshake (1 RTT) · seq + ACK + retransmit · in-order · flow ctl (receiver) · congestion ctl (network)
  • Cost: setup RTTs, slow start (IW 10 segments), head-of-line blocking
  • Teardown: FIN/ACK each way · closer waits in TIME_WAIT (2 x MSL; Linux 60 s)
  • UDP: 8-byte header (ports, length, checksum) · no delivery, order or state
  • UDP apps rebuild: IDs, seq numbers, selective resend or skip, rate control
  • QUIC = UDP + reliability + TLS 1.3 in user space · per-stream order · 1-RTT (0 on resume) · migration
  • Pool connections: fresh HTTPS ≈ 3 RTT vs reused ≈ 1 RTT
  • TCP: APIs, payments, chat · UDP: voice/video, game state, DNS, metrics
Further reading (6)