🚚 TCP vs UDP

Two transports, two philosophies: a reliable ordered stream versus a fast, lossy datagram. Almost everything above the network layer inherits one of these.

The Split

Both TCP and UDP sit on top of IP, which by itself is unreliable: packets can be dropped, duplicated, delayed, or reordered. TCP and UDP make opposite bets about what to do with that.

TCP gives you a reliable, ordered byte stream and hides packet loss from you β€” at the cost of latency and head-of-line blocking. UDP gives you raw datagrams with no guarantees β€” and hands you the steering wheel.
PropertyTCPUDP
ConnectionYes β€” handshake firstNo β€” just send
ReliabilityRetransmits lost dataNone β€” app's problem
OrderingIn-order deliveryMay arrive reordered
BoundariesByte stream (no message frames)Preserves datagram boundaries
Flow / congestion controlBuilt inNone
Header size20+ bytes8 bytes
Head-of-line blockingYesNo

The 3-Way Handshake

TCP is connection-oriented: before any data moves, both sides synchronize sequence numbers so they can detect loss and reordering. That costs one round trip before the first byte.

sequenceDiagram participant C as Client participant S as Server C->>S: SYN (seq=x) S->>C: SYN-ACK (seq=y, ack=x+1) C->>S: ACK (ack=y+1) Note over C,S: Connection established β€” 1 RTT spent C->>S: data (application bytes)
Why it matters: that RTT is pure setup latency. Over TLS it gets worse β€” see TLS, where the crypto handshake stacks on top. Collapsing these round trips is exactly what HTTP/3 over QUIC is built to do.

UDP has no equivalent. You call sendto() and the datagram leaves immediately β€” zero setup RTT.

Reliability, in Pieces

TCP's "reliability" is really four mechanisms working together:

The cost of ordering: if segment #2 is lost, TCP will hold back #3, #4, #5 that already arrived β€” the application can't read them until the gap is filled. That's head-of-line blocking, and it's unavoidable in a single TCP stream. It's the core reason HTTP/2 still stalls under packet loss despite multiplexing.

Flow Control vs Congestion Control

Two different problems, often confused:

Flow controlCongestion control
ProtectsThe receiver from being overrunThe network from being overrun
MechanismAdvertised receive window (rwnd)Congestion window (cwnd)
SignalReceiver's buffer spacePacket loss / delay (Reno, CUBIC, BBR)

The effective send rate is bounded by min(rwnd, cwnd). Congestion control starts in slow start (exponential ramp) then switches to congestion avoidance (linear), backing off hard on loss. This is why a fresh connection is slow β€” cwnd has to warm up.

Sending a Datagram (UDP)

UDP's API is almost nothing β€” no connect, no accept, no stream. You get message boundaries for free.

import socket

# UDP: connectionless, boundary-preserving, no delivery guarantee
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(b"PING", ("10.0.0.5", 9999))   # fire and forget β€” may never arrive

data, addr = sock.recvfrom(1500)           # one recv == one datagram
print(data, "from", addr)

Compare TCP, where you connect first and read an undelimited stream:

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("10.0.0.5", 9999))    # triggers the 3-way handshake
sock.sendall(b"hello world")        # bytes, not messages
chunk = sock.recv(4096)             # may return a partial "message" β€” you frame it yourself
No message framing in TCP: recv() can return half a message or two messages glued together. Every TCP protocol invents its own framing β€” length prefixes, delimiters, or a self-describing format like protobuf on the wire.

When Each Wins

Use TCP when…Use UDP when…
Every byte must arrive, in order (HTTP, DBs, file transfer)Fresh data beats late data (voice, video, game state)
You want the OS to handle loss & orderingYou'll build your own reliability (QUIC, DNS, custom protocols)
Long-lived, high-throughput streamsTiny request/response or multicast
The twist: QUIC is built on UDP precisely to escape TCP's kernel-level head-of-line blocking β€” then rebuilds reliability, ordering, and congestion control in user space, per-stream. UDP wasn't chosen for speed; it was chosen for a blank slate.

Key Takeaways