Skip to main content

TCP vs UDP: Reliability vs Speed

Easy

Interview Question: "What is the fundamental difference between TCP and UDP? Compare their header overheads, explain the difference between a byte stream and datagram boundaries, and explain why high-frequency trading chooses UDP."

Think about the difference between certified mail and a postcard. Certified mail gets a signature when it arrives — if it never shows up, you find out and can resend it. A postcard just goes in the mailbox — no confirmation, no resend if it's lost, but it's faster and cheaper to send.

That's TCP and UDP. TCP trades speed for reliability, order, and integrity. UDP trades reliability for speed and simplicity. Both sit at Layer 4 (Transport).

TCP
reliable, ordered
UDP
fast, no guarantees

What's inside each header​

  • TCP header: at least 20 bytes (up to 60 with options). Carries sequence numbers, acknowledgment numbers, flags (SYN, ACK, FIN, RST), and the advertised window size — everything needed to track order and confirm delivery.
  • UDP header: a fixed 8 bytes. Just source port, destination port, length, and an optional checksum. Roughly 60% less overhead per packet than TCP, so more of every packet is actual payload.

Byte stream vs. datagram boundaries​

This is a common follow-up question, because it trips people up:

  • TCP is just a stream of bytes — it doesn't know where one message ends and another begins. If you call send("Hello") then send("World"), the receiver might get "HelloWorld" in one recv() call, or "Hel" and then "loWorld". If your app needs to know where messages start and stop, you have to build that yourself — a length prefix, or a delimiter like \r\n.
  • UDP keeps each message as its own separate packet. One sendto() call produces exactly one datagram, and one recvfrom() call reads exactly one datagram, in full, with its boundaries intact.

Comparison​

FeatureTCPUDP
ConnectionConnection-oriented (3-way handshake)Connectionless — fire and forget
ReliabilityGuaranteed — lost packets get retransmittedNo guarantee — packets can silently drop
OrderingStrict, via sequence numbersNone — packets can arrive out of order
Header size20 to 60 bytesFixed 8 bytes
Flow & congestion controlBuilt in (sliding window, slow start, AIMD)None — the app has to throttle itself
Data framingByte stream, no boundariesIndependent datagrams, boundaries preserved
TransmissionUnicast onlyUnicast, multicast, and broadcast

Why high-frequency trading and video calls pick UDP​

"Late data is worse than lost data."

  1. Market ticks: exchanges broadcast live price feeds over UDP multicast. If a packet drops on a TCP connection, TCP stops, asks for a retransmit, and waits. By the time that packet shows up 5ms later, the price has already moved — the "recovered" data is stale and useless. It's better to drop the missed tick and move straight to the next real one.
  2. Video calls (Zoom, WebRTC): if one audio frame drops, retransmitting it 200ms later causes a jarring stutter. A tiny, barely-noticeable glitch beats growing lag.

Say this in the interview​

"TCP gives you reliable, ordered, byte-stream delivery with built-in flow and congestion control, at the cost of a handshake and a bigger header. UDP gives you lightweight, connectionless datagram delivery with a fixed 8-byte header — you give up reliability in exchange for raw speed, which is exactly what real-time streaming, DNS, and market-data feeds need."


Code: TCP stream vs. UDP datagram​

import socket
import threading
import time

def run_udp_demo():
# UDP Server: Preserves discrete datagram boundaries
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(("127.0.0.1", 9999))

def client_send():
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Two distinct datagram sends
client.sendto(b"Message 1", ("127.0.0.1", 9999))
client.sendto(b"Message 2", ("127.0.0.1", 9999))
client.close()

threading.Thread(target=client_send).start()

# Server receives exactly two distinct datagrams
data1, _ = server.recvfrom(1024)
data2, _ = server.recvfrom(1024)
print(f"[UDP] Received Datagram 1: {data1.decode()}")
print(f"[UDP] Received Datagram 2: {data2.decode()}")
server.close()

if __name__ == "__main__":
run_udp_demo()