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.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *