UDP vs TCP: How Much Faster Is UDP Really?
TLDR: UDP can be 2-10x faster than TCP for small messages in ideal conditions, but this advantage shrinks or disappears when you add the reliability features your application needs. TCP's overhead is around 20-40 bytes per packet plus connection setup, while UDP uses only 8 bytes. The real difference comes from TCP's flow control, congestion management, and retransmission logic, not just header size.
When you're choosing between UDP and TCP for your application, the question "how much faster is UDP?" comes up constantly. The answer isn't straightforward because it depends heavily on your network conditions, message size, and what reliability features you need to implement on top of UDP.
What Makes UDP Faster
UDP (User Datagram Protocol) is connectionless and stateless. When you send a UDP packet, it goes straight out without handshakes, acknowledgments, or retransmission logic. Here's what UDP skips:
TCP Connection Flow:
Client Server
| |
|----SYN---------------> | (1 RTT)
|<---SYN-ACK-----------| |
|----ACK---------------> |
|----Data--------------> | (+ data transfer)
|<---ACK----------------| |
UDP Flow:
Client Server
| |
|----Data--------------> | (immediate)
|----Data--------------> |
TCP requires a three-way handshake before any data moves. That's one full round-trip time (RTT) of overhead before your first byte arrives. For a local network where RTT might be 1ms, this is negligible. For a connection across continents where RTT might be 200ms, that's a noticeable delay.
Header Overhead Comparison
The header size difference is often cited but is rarely the bottleneck:
# UDP header structure (8 bytes total)
class UDPHeader:
source_port # 2 bytes
dest_port # 2 bytes
length # 2 bytes
checksum # 2 bytes
# TCP header structure (minimum 20 bytes, typically 32+ with options)
class TCPHeader:
source_port # 2 bytes
dest_port # 2 bytes
sequence_num # 4 bytes
ack_num # 4 bytes
flags # 2 bytes
window_size # 2 bytes
checksum # 2 bytes
urgent_pointer # 2 bytes
options # 0-40 bytes (often 12 bytes for timestamps)
For a 1500-byte packet, TCP uses about 32 bytes (2.1%) while UDP uses 8 bytes (0.5%). This difference is minimal. For tiny packets like gaming updates or DNS queries, the percentage is higher but still not the main factor.
Real Performance Numbers
Let's look at actual measurements. Here's a simple benchmark sending 10,000 messages between two processes on localhost:
import socket
import time
def benchmark_udp(message_size, count):
"""Send messages via UDP and measure throughput."""
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
data = b'x' * message_size
start = time.time()
for _ in range(count):
sock.sendto(data, ('127.0.0.1', 9999))
elapsed = time.time() - start
sock.close()
return count / elapsed # messages per second
def benchmark_tcp(message_size, count):
"""Send messages via TCP and measure throughput."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 9998))
data = b'x' * message_size
start = time.time()
for _ in range(count):
sock.sendall(data)
elapsed = time.time() - start
sock.close()
return count / elapsed
# Example results on a modern Linux system
# 100-byte messages:
# UDP: ~180,000 msg/sec
# TCP: ~120,000 msg/sec (1.5x slower)
# 1500-byte messages:
# UDP: ~85,000 msg/sec
# TCP: ~75,000 msg/sec (1.13x slower)
# 8KB messages:
# UDP: ~28,000 msg/sec
# TCP: ~27,000 msg/sec (nearly identical)
The performance gap narrows as message size increases because the overhead becomes proportionally smaller. For large transfers, TCP can actually be faster because its congestion control prevents overwhelming the network.
Where TCP's "Slowness" Comes From
TCP's performance characteristics come from features that UDP simply doesn't have:
Flow Control: TCP adjusts its sending rate based on the receiver's buffer space. This prevents overwhelming a slow receiver but adds computation and can throttle the sender.
Congestion Control: TCP backs off when it detects network congestion. This is good for network health but can significantly reduce throughput during congestion events.
Ordered Delivery: TCP maintains sequence numbers and reorders packets. If packet 5 arrives before packet 4, TCP holds packet 5 until packet 4 shows up. This head-of-line blocking can add latency.
Retransmission: When a packet is lost, TCP detects it (via timeout or duplicate ACKs) and resends it. The detection and retransmission add latency that compounds with RTT.
Here's what happens when a packet is lost:
TCP with 1% packet loss (100ms RTT):
Send packets 1-10
Packet 5 is lost
After ~100ms, sender realizes via missing ACK
Resend packet 5
Wait another ~100ms for ACK
Total delay: ~200ms for that packet
UDP with 1% packet loss:
Send packets 1-10
Packet 5 is lost
Application never knows (unless you implement detection)
No automatic recovery
When UDP Is Actually Faster
UDP shines in specific scenarios:
Small, Independent Messages: DNS queries, game state updates, or sensor data where each message is self-contained and occasional loss is acceptable.
# DNS query - perfect for UDP
def dns_lookup(domain):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(2.0)
query = build_dns_query(domain)
sock.sendto(query, ('8.8.8.8', 53))
try:
response, _ = sock.recvfrom(512)
return parse_dns_response(response)
except socket.timeout:
return None # Retry or fail - simple logic
Multicast and Broadcast: TCP can't do multicast. If you need to send the same data to multiple receivers efficiently, UDP is your only option.
Real-Time Streams: Video conferencing or live streaming where old data is worthless. Better to skip a lost frame than wait for retransmission.
# Video streaming - late frames are worthless
def stream_video_udp(sock, video_source):
frame_num = 0
while True:
frame = video_source.get_next_frame()
# Add frame number so receiver can detect gaps
packet = struct.pack('!I', frame_num) + frame
sock.sendto(packet, receiver_address)
frame_num += 1
time.sleep(1/30) # 30 fps
# No waiting for ACKs - if a frame is lost,
# the next frame is more valuable than resending
When TCP Is Actually Faster
Counterintuitively, TCP can outperform UDP in several situations:
Reliable Bulk Transfer: If you need reliable delivery and implement your own acknowledgment system on UDP, you're basically rebuilding TCP poorly. TCP's implementation is highly optimized.
Congested Networks: TCP's congestion control prevents packet loss. If you send UDP too fast and cause packet loss, you waste bandwidth on data that never arrives.
Long-Lived Connections: The connection setup cost amortizes over time. A web API that maintains persistent connections doesn't pay the handshake cost repeatedly.
Building Reliability on UDP
If you need reliability with UDP, you'll add overhead that erodes the speed advantage:
class ReliableUDP:
"""A simplified reliable UDP implementation."""
def __init__(self, sock):
self.sock = sock
self.sequence = 0
self.pending_acks = {}
self.receive_buffer = {}
def send_reliable(self, data, addr):
"""Send data and wait for acknowledgment."""
seq = self.sequence
self.sequence += 1
# Add sequence number to packet
packet = struct.pack('!I', seq) + data
# Send and wait for ACK (with retries)
max_retries = 3
for attempt in range(max_retries):
self.sock.sendto(packet, addr)
# Wait for ACK
self.sock.settimeout(0.5)
try:
ack_packet, _ = self.sock.recvfrom(1024)
ack_seq = struct.unpack('!I', ack_packet[:4])[0]
if ack_seq == seq:
return True
except socket.timeout:
continue
return False # Failed after retries
# Now you've added:
# - Sequence numbers (like TCP)
# - Acknowledgments (like TCP)
# - Retransmission (like TCP)
# - Timeout logic (like TCP)
#
# You've rebuilt TCP's overhead, but less efficiently
This is why protocols like QUIC exist - they use UDP as a transport but implement TCP-like reliability features at the application layer with better performance characteristics than kernel-level TCP.
Practical Recommendations
Choose UDP when:
- You're sending small, independent messages
- Occasional data loss is acceptable
- You need multicast or broadcast
- You're implementing a custom protocol (like QUIC) with specific reliability needs
- Latency is more important than reliability
Choose TCP when:
- You need reliable, ordered delivery
- You're transferring files or bulk data
- You don't want to implement reliability logic yourself
- You need the connection state that TCP provides
- You're building a standard request-response API
The speed difference between UDP and TCP is real but often overstated. In many real-world scenarios, the difference is less than 2x, and once you add necessary reliability features to UDP, TCP's battle-tested implementation often wins.
Your network conditions, message patterns, and reliability requirements matter far more than the raw protocol speed. Profile your specific use case before optimizing.
Found an issue?