UDP — Fast and Stateless
UDP (User Datagram Protocol) is what you use when speed matters more than reliability. There is no connection. There are no acknowledgments. You send a datagram. It either arrives or it doesn’t. If you need to know, that’s your application’s job.
What UDP is
- Connectionless — no handshake. Just send.
- Unreliable — no retransmission. Lost packets stay lost.
- Unordered — packets arrive in whatever order the network delivers them.
- Stateless — no per-flow state on the kernel.
- Tiny header — 8 bytes (vs TCP’s 20+).
The UDP header
┌─────────────────────┬─────────────────────┐
│ Source port (16) │ Dest port (16) │
├─────────────────────┼─────────────────────┤
│ Length (16) │ Checksum (16) │
└─────────────────────┴─────────────────────┘
[ payload ]
That’s it. Eight bytes of overhead.
When UDP is the right choice
1. Real-time media (voice, video)
If a video frame is 200 ms late, you don’t want it — you want the next frame. Retransmitting old data wastes bandwidth and makes things worse. Apps handle their own loss tolerance (FEC, jitter buffers, codec resilience).
2. DNS queries
DNS queries are tiny (a few hundred bytes). Setting up a TCP connection just to ask “what’s the IP for example.com?” would triple the latency. If a UDP query is lost, the resolver retries.
3. Online games
Player position updates 60 times per second. If one update is lost, the next one is already on its way with newer data. TCP would force the game to wait for the missing update.
4. Logging / metrics (statsd, syslog)
If a single metric packet is lost, you don’t care. Don’t waste TCP overhead on every counter increment.
5. Peer-to-peer / NAT traversal
UDP holes punch through NAT more reliably than TCP. WebRTC, gaming P2P, BitTorrent DHT all use UDP for this.
6. QUIC (HTTP/3)
Modern web traffic. Built on UDP because TCP couldn’t be evolved fast enough.
Programming with UDP
The API is dramatically simpler than TCP. Python example:
import socket
# Server
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('0.0.0.0', 5000))
data, addr = s.recvfrom(1024)
print(f"Got {data} from {addr}")
s.sendto(b"reply", addr)
# Client
c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
c.sendto(b"hello", ('server.com', 5000))
reply, _ = c.recvfrom(1024)
No accept(). No close(). Each datagram is independent.
Common gotchas
- Datagram size limits — UDP can fragment, but most networks drop fragmented UDP. Keep payloads under ~1400 bytes for reliable delivery.
- No backpressure — your sender can flood the receiver and the network. Application is responsible for rate limiting.
- Firewalls are stricter on UDP because there’s no connection state to track. Many corporate networks block all UDP except DNS.
Test UDP connectivity
# Server (using netcat)
nc -ul 5000
# Client
echo "test" | nc -u server.com 5000
# Or with iperf3
iperf3 -s
iperf3 -c server.com -u -b 100M # UDP test at 100 Mbps
See open UDP sockets
ss -un # all UDP sockets
ss -ulnp # listening UDP sockets, with process name
What to learn next
The TCP 3-way handshake — exactly how a TCP connection actually starts. Up next.