Skip to content

TCP vs UDP comparison

Every time an application opens a socket, something has already made a decision on its behalf: reliable or fast. Not both, not really — that trade-off is baked into the two protocols that sit at the Transport layer of the TCP/IP model, and almost nothing you do on a network skips past it. A browser loading a page, a phone call over the internet, a game server broadcasting player positions, a database driver opening a connection — each one is quietly built on top of either TCP or UDP, and the choice shapes everything above it.

This article compares the two protocols directly: what each one actually puts on the wire, what guarantees they make and don't make, and how to reason about which one a given piece of software should use. The two articles that follow it go deeper on each side — UDP for streaming, DNS and games and TCP for file transfer and APIs — but you need this comparison first, because most of what makes each protocol suited to its use cases only makes sense side by side.

Same job, opposite philosophy

Both protocols solve the same basic problem: ports tell a machine which running process a piece of data is meant for, and both TCP and UDP carry a source and destination port to make that delivery possible. Beyond that, they diverge almost completely.

UDP, defined in 1980 by RFC 768, is about as close to "just send it" as a transport protocol gets. It has no concept of a connection. It doesn't check whether the destination is listening, doesn't confirm anything arrived, and doesn't care what order packets show up in. Each datagram is independent — hence the name, User Datagram Protocol.

TCP, standardized a year later in RFC 793 and now specified by RFC 9293 (which folded in four decades of clarifications and errata), does the opposite. It doesn't just send data — it builds a relationship first. Before a single byte of application data moves, the two sides run a handshake to agree they're both present and ready to talk.

From there, everything TCP does is in service of one goal: make the connection behave like a perfect, ordered stream of bytes, even though the network underneath is neither. It gets there with four separate mechanisms, and it's worth seeing them as four separate jobs rather than one blur:

  • Every byte gets a sequence number, so the receiver can tell what order things belong in.
  • Every segment gets acknowledged, so the sender knows what actually arrived.
  • Anything that isn't acknowledged in time gets retransmitted automatically.
  • The sender paces itself using flow control and congestion control, so it never sends faster than the receiver, or the network in between, can handle.

TCP guarantees delivery and order; UDP fires and forgets. Everything else in this article is detail on top of that one sentence.

Neither protocol is a better version of the other. They were designed to make opposite trade-offs on purpose, and the fact that both still exist, essentially unchanged in their core design for over forty years, is itself evidence that both trade-offs are still needed.

What's actually on the wire

The clearest way to see the difference is to look at what each protocol adds to a packet before handing it to IP.

The UDP header: 8 bytes, no more

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Source Port          |       Destination Port       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|            Length             |           Checksum           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                             data                              |

That's the whole header, fixed at 8 bytes: source port, destination port, a length field, and a checksum. There's nowhere in that layout to put a sequence number, an acknowledgment number, or a window size, because UDP doesn't track any of those things. The checksum is optional in IPv4 (though every mainstream stack enables it) and mandatory in IPv6 — but even when present, all it tells the receiver is "this datagram wasn't corrupted in transit." It says nothing about whether a previous datagram arrived, or in what order this one showed up relative to others.

The TCP header: 20 bytes minimum, up to 60

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Source Port          |       Destination Port       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Sequence Number                       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Acknowledgment Number                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Offset|Rsrvd|   Flags   |            Window                   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Checksum            |        Urgent Pointer        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Options (if any)                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Every one of those extra fields exists to support a guarantee UDP doesn't make. The sequence number lets the receiver put data back in order and detect gaps. The acknowledgment number tells the sender exactly how much data has been confirmed received. The flags field carries the control bits (SYN, ACK, FIN, RST, and others) that manage connection setup, teardown, and resets. The window field is how the receiver tells the sender "this is how much more I can buffer right now" — the basis of flow control. Options, when present, can add things like the maximum segment size the sender is willing to receive, pushing the header past its 20-byte minimum and up to 60.

That header cost isn't the only cost, either. Every one of those fields also requires state: the operating system on both ends has to remember the current sequence number, the last acknowledgment sent, the negotiated window size, and more, for as long as the connection stays open. UDP doesn't remember anything between one datagram and the next — that's what "stateless" means in practice, not just a description on a slide.

Connection-oriented vs connectionless

This is the distinction the header fields exist to serve, and it explains almost every other difference on this page.

TCP is connection-oriented: before data flows, both sides complete a three-way handshake (SYN, SYN-ACK, ACK) that establishes shared state — starting sequence numbers on both sides — and closing a connection is its own negotiated process, a four-step exchange of FIN and ACK segments. Everything TCP guarantees afterward depends on that state existing. Send a segment claiming to belong to a connection the receiving host has no record of, and the reply is a RST (reset) — TCP won't process data for a connection it doesn't recognize.

UDP is connectionless. There's no setup phase and no teardown. A UDP socket can send a datagram to a destination it has never contacted before, and the destination will attempt to deliver it to whatever process is listening on that port — no prior agreement required. This is exactly why DNS resolution works the way it does: your resolver doesn't "connect" to a DNS server first. It sends one UDP datagram containing the query and waits for one UDP datagram back. If nothing comes back within a timeout, it just tries again or asks a different server — there's no connection to tear down or recover.

Reliability, ordering, and flow control

Three related guarantees, all absent from UDP and all present in TCP:

  • Reliability. TCP retransmits any segment that isn't acknowledged within a calculated timeout. UDP delivers each datagram at most once, best-effort — if it's lost in transit, nothing at either end notices or resends it, unless the application built its own retry logic on top.
  • Ordering. TCP's sequence numbers let the receiving stack reassemble segments in the order they were sent, even if the underlying IP packets arrived out of order, which happens routinely since each packet can take a different path across the network. UDP datagrams are handed to the application in whatever order they arrive, with no reordering step at all.
  • Flow and congestion control. TCP continuously adjusts how much unacknowledged data it will have in flight, based on the receiver's advertised window (flow control — don't overwhelm the receiving application) and on inferred network conditions (congestion control — don't overwhelm the path in between). UDP has neither. A UDP sender can push data as fast as the local network interface allows, regardless of whether the receiver or the network can keep up.

None of this makes TCP "better." It makes TCP suited to situations where getting everything, in order, matters more than getting it fast — and it makes UDP suited to the opposite. TCP for file transfer and APIs and UDP for streaming, DNS and games each dig into what that means for a specific class of application.

Side-by-side comparison

Property TCP UDP
Connection model Connection-oriented (handshake, then teardown) Connectionless
Header size 20 bytes minimum, up to 60 with options 8 bytes, fixed
Delivery guarantee Retransmits lost segments Best-effort, no retransmission
Ordering Reassembled in order at the receiver Delivered in arrival order
Flow control Yes, via the receiver's advertised window None
Congestion control Yes, adapts sending rate to network conditions None
State kept per connection Yes — sequence numbers, window, timers None
First-byte latency Higher — handshake costs a round trip before data Lower — no setup delay
Forging a session Hard — requires guessing in-window sequence numbers Easy — no session state to forge against
Defined in RFC 9293 (obsoletes RFC 793) RFC 768

Seeing the difference on the wire

Reading about headers is one thing; watching them arrive is another. On a Linux machine with tcpdump installed, capturing a DNS lookup shows UDP with no setup at all:

sudo tcpdump -i any -n port 53 -c 2
14:02:11.442013 IP 192.168.1.10.51820 > 192.168.1.1.53: 34521+ A? example.com. (29)
14:02:11.478559 IP 192.168.1.1.53 > 192.168.1.10.51820: 34521 1/0/0 A 93.184.216.34 (45)

Two lines, total. A query goes out, an answer comes back, and that's the entire conversation — no prior packets establishing anything. tcpdump needs root (or the CAP_NET_RAW capability) because reading raw frames off a network interface bypasses the normal per-socket permission model entirely; without elevated privileges a process can only see traffic addressed to its own sockets, not everything crossing the interface.

Now capture an HTTPS connection to compare:

sudo tcpdump -i any -n host example.com and port 443 -c 6
14:05:02.100112 IP 192.168.1.10.51900 > 93.184.216.34.443: Flags [S], seq 1391027381, win 64240
14:05:02.134870 IP 93.184.216.34.443 > 192.168.1.10.51900: Flags [S.], seq 2847113920, ack 1391027382, win 65160
14:05:02.135003 IP 192.168.1.10.51900 > 93.184.216.34.443: Flags [.], ack 2847113921, win 64240
14:05:02.135560 IP 192.168.1.10.51900 > 93.184.216.34.443: Flags [P.], seq 1391027382:1391027523, ack 2847113921, win 64240
14:05:02.169840 IP 93.184.216.34.443 > 192.168.1.10.51900: Flags [.], ack 1391027523, win 65535
14:05:02.201229 IP 93.184.216.34.443 > 192.168.1.10.51900: Flags [P.], seq 2847113921:2847114260, ack 1391027523, win 65535

Before any TLS data moves, three lines pass with no payload at all — [S], [S.], [.] — the three-way handshake. Every following line carries a sequence number, an acknowledgment number, and a window size, none of which exist in the DNS capture above. That's the entire cost-and-benefit argument for TCP made visible: two extra round trips before data flows, in exchange for every byte after that being tracked, ordered, and confirmed. Later articles in this module take the handshake, the sequence numbers, and the window field apart individually — this comparison only needs you to recognize the shape of the exchange.

Your own capture will show different port numbers, sequence numbers, and timestamps — those are assigned per connection and won't match another run, even against the same server.

Choosing between them

In practice the decision comes down to a short set of questions, asked in this order:

  1. Can the application tolerate losing some data outright? If a dropped piece of data is genuinely fine to lose — a single video frame, one player-position update — UDP's lack of retransmission is a feature, not a gap. If losing any of it corrupts the result (a file, a database write, a JSON payload), TCP's guarantee is what you need.
  2. Does correctness depend on strict ordering? TCP reorders for you. Anything running over UDP that cares about order has to add sequence numbers and reordering logic at the application layer itself — real, non-trivial work.
  3. Is the exchange short-lived and one-shot, or long and conversational? A single request-response exchange, like a DNS query, pays TCP's handshake cost on every interaction, whereas a long HTTP keep-alive connection or an SSH session pays that cost once and amortizes it across everything that follows. Short, frequent exchanges favor UDP; long conversations favor TCP.
  4. Does the network path include NAT or firewalls that expect connection state? UDP does work through NAT, but because it has no connection concept, a NAT device has to guess when a UDP "session" has ended, usually via an idle timeout — which is why a long-idle UDP-based call sometimes drops unexpectedly, where an equivalent TCP connection would either stay open or fail visibly.

One misconception is worth heading off before the next two articles build on it: "UDP means unreliable, full stop" isn't quite right. UDP itself makes no reliability guarantee, but nothing stops an application from building one on top of it. QUIC, specified in RFC 9000 and the transport underneath HTTP/3, does exactly that — it runs over UDP but adds its own sequencing, acknowledgment, and retransmission, engineered around specific problems in TCP that this module's later material covers in depth. The real lesson isn't "UDP is unreliable and TCP is reliable" as a law of nature; it's that TCP provides reliability inside the transport layer itself, while UDP leaves that decision, and that engineering cost, to whoever builds on top of it.

Where this goes next

Knowing the two headers and the four guarantees is the foundation, but it doesn't yet explain why a video-call engineer reaches for UDP while a payments API engineer won't go near it. That's a matter of matching each protocol's trade-offs to a real workload, which is exactly what the next two articles do: UDP for streaming, DNS and games walks through the applications that accept UDP's risks for its speed, and TCP for file transfer and APIs walks through the ones that need every guarantee TCP makes and are willing to pay for it.

Sources