Source: lessons/day-03-tcp-udp-websockets-sse.md

Day 3 — TCP vs UDP, WebSockets, Long Polling, SSE

Goal: Own the transport choices under every network design. By the end you should be able to: - Explain TCP vs UDP — precisely and with trade-offs - Choose the right server-push mechanism (Long Polling / SSE / WebSockets) for a given problem - Understand why real-time systems (chat, gaming, video) pick specific transports - Answer the classic FAANG probe: "how do you push data from server to client?"

Estimated time: 50–60 min read + reflection.


1. Where TCP and UDP sit — quick stack recap

Application:  HTTP, HTTPS, WebSocket, gRPC, DNS, SSH, Redis protocol …
Transport:    TCP           |    UDP              |    QUIC (new, over UDP)
Network:                      IP
Link:                    Ethernet / Wi-Fi

The transport layer is what turns "put these bytes in a wire" (IP) into "deliver these bytes to a program on another machine reliably (or not)." Almost everything you build sits on top of TCP or UDP.


2. TCP — Transmission Control Protocol

Contract: "I will deliver your bytes to the other end in order, without gaps, without duplicates — or tell you the connection is dead."

TCP guarantees

  1. Connection-oriented — a session is established (3-way handshake) and torn down explicitly.
  2. Reliable delivery — lost segments are retransmitted (uses ACKs + sequence numbers).
  3. In-order delivery — the receiver reassembles segments in the order sent, even if they arrive out of order.
  4. Flow control — receiver tells sender "here's my available buffer" via the advertised window, so a fast sender doesn't overwhelm a slow receiver.
  5. Congestion control — sender backs off when the network is congested (slow-start, AIMD, cubic, BBR). Protects the internet from meltdown.
  6. Full-duplex — data can flow both directions simultaneously.

The 3-way handshake

Client                                  Server
  │─── SYN ─────────────────────────────▶│    "I want to talk, my seq starts at X"
  │◀── SYN-ACK ─────────────────────────│    "OK, my seq starts at Y, I ack yours"
  │─── ACK ─────────────────────────────▶│    "I ack yours. Let's go."

Cost: 1 RTT before you can send any real data. On a 150 ms cross-continent link, that's a 150 ms hit before you even start.

TCP costs

  • Handshake latency (1 RTT).
  • Retransmit latency — if a packet drops, the receiver holds up later packets until the missing one arrives. This is head-of-line blocking.
  • Larger headers (~20 bytes) vs UDP (~8 bytes).
  • Kernel buffer memory per connection (thousands of connections = real RAM).
  • Slow start — sender begins with a small congestion window and grows it. Big transfers ramp up; first few packets are the bottleneck.

When TCP is the right choice

  • Any workload where byte-for-byte correctness matters and moderate latency is OK: HTTP(S), file transfer, database wire protocols, email, SSH, Git, Redis, Kafka.
  • Basically: 99% of general-purpose applications.

3. UDP — User Datagram Protocol

Contract: "I will try to deliver your datagram to the other end. No promises about arrival, order, or duplicates. You handle that yourself if you care."

UDP characteristics

  1. Connectionless — no handshake, no session state. Each datagram is independent.
  2. Unreliable — packets can be lost, and no retry happens by the transport.
  3. Unordered — packets can arrive out of order (or not at all).
  4. No congestion control — sender can blast at wire speed if the app doesn't self-limit.
  5. Tiny header (~8 bytes).
  6. Fire-and-forget — send a datagram, no wait for ACK.

Consequences

  • Low latency — no handshake, no ACK wait. You send, it arrives (probably) as fast as the network can carry it.
  • Head-of-line blocking is gone — if one datagram is lost, subsequent datagrams still get through and can be used.
  • You own reliability if you need it — apps that need "some" reliability build it on top (QUIC does this for HTTP/3).

When UDP is the right choice

  • DNS — small queries, retries are cheap, one RTT budget is precious.
  • Real-time media (VoIP, video calls, WebRTC) — losing a video frame is fine; retransmitting it 300 ms late is worse than skipping it.
  • Online gaming — position updates 60×/sec; a stale packet is useless, drop and use the newer one.
  • Time-critical telemetry (metric emission, syslog UDP mode) — better to lose 1% of samples than to block on retries.
  • QUIC — Google's transport that gives you HTTP-like reliability and low latency, built over UDP. Basis of HTTP/3.

TCP vs UDP — quick decision matrix

You need… Pick
Byte-for-byte correctness (files, DB) TCP
Interactive human-timed workloads (RPC, browsing) TCP
Real-time media where stale > missing UDP
Fire-and-forget small messages UDP
Custom reliability on top of low latency UDP (or QUIC)
Massive concurrent clients with minimal state UDP

4. The problem HTTP doesn't solve — server push

HTTP is request/response: the client always asks, the server always replies. But many apps need the server to tell the client about something (a new chat message, a stock tick, a live score update, a notification). HTTP alone can't do that without the client asking again.

So the internet has invented four mechanisms to get server → client pushes on top of HTTP:

  1. Short polling — client asks every N seconds
  2. Long polling — client asks; server waits until it has something
  3. Server-Sent Events (SSE) — HTTP-native one-way stream from server to client
  4. WebSockets — full-duplex, low-overhead, both directions after an HTTP upgrade

Let's take them one by one.


5. Short polling — the naive baseline

Client                                Server
  │─ GET /messages?since=T1 ──▶ │
  │◀─ [] ────────────────────── │   (nothing yet)
  │              sleep 5 s              │
  │─ GET /messages?since=T1 ──▶ │
  │◀─ [] ────────────────────── │
  │              sleep 5 s              │
  │─ GET /messages?since=T1 ──▶ │
  │◀─ [{msg1}] ──────────────── │   (got one!)
  • Pros: trivial to implement, works with any HTTP server.
  • Cons: wastes requests (99% are empty), latency = polling interval (5 s in the example is 5 s of user-visible lag), scales poorly (N clients × 1 req/interval = big load).
  • When acceptable: low-frequency updates where a few seconds' lag is fine (dashboards refreshing every 30 s).

6. Long polling — "hold the connection until you have news"

The client sends a request; the server holds it open (doesn't respond) until either (a) there's an update, or (b) a timeout hits. When either happens, the server responds and the client immediately opens another request.

Client                                Server
  │─ GET /messages ─────────▶ │
  │                                     │  (server waits, no response yet…)
  │                                     │  (still waiting…)
  │◀─ {msg1} ─────────────── │  (new message! respond)
  │─ GET /messages ─────────▶ │  (client immediately re-connects)
  │                                     │  (waits again…)
  • Pros: near-real-time delivery, works over any HTTP infra (proxies, LBs).
  • Cons: each server holds many open connections (memory pressure); a new HTTP request per delivery (overhead); no server → client push mid-request (you always wait for the next re-poll cycle).
  • When to use: simpler apps where WebSockets are overkill or blocked by corporate firewalls.

7. Server-Sent Events (SSE) — one-way HTTP stream

The client makes one HTTP request; the server responds with Content-Type: text/event-stream and keeps the connection open, writing events as they happen. Built into browsers (EventSource API). It's still HTTP, just… streamed forever.

Client                                Server
  │─ GET /events ──────────▶ │
  │◀─ HTTP 200                       │
  │   Content-Type: text/event-stream│
  │                                     │
  │◀─ data: {msg1} ──────────│  (immediately when it happens)
  │◀─ data: {msg2} ──────────│
  │◀─ data: {msg3} ──────────│
  │                     … forever ...  │
  • Pros: HTTP-native (works with LBs, CDNs, HTTP/2 multiplexing); auto-reconnect; simple browser API; low overhead per event (just text); one connection, many events.
  • Cons: one-way only (server → client). Client → server still uses a normal HTTP POST. Not supported in some old browsers (mostly irrelevant now).
  • When to use: notifications, live feeds, dashboards, activity streams, log tailing, LLM token streaming (ChatGPT-style responses use SSE!).

8. WebSockets — full-duplex, low overhead, real-time

The client makes an HTTP request with Upgrade: websocket. The server responds HTTP 101 Switching Protocols. From then on, the same TCP connection carries WebSocket frames (both directions) — HTTP is out of the picture.

Client                                Server
  │─ GET /ws                                  │
  │   Upgrade: websocket ──────▶│
  │◀─ HTTP 101 Switching Protocols │
  │  ═════════════════════════│  (now WebSocket frames, both ways)
  │─ frame: "hi"         ─────▶ │
  │◀─ frame: "hello"    ──────  │
  │─ frame: "typing…"   ─────▶ │
  │◀─ frame: {new_msg} ──────  │
  • Pros: true full-duplex, minimal per-message overhead (~2–14 bytes of framing), low latency (no HTTP round-trip per message), works well for chat / gaming / collaboration / trading.
  • Cons: long-lived connections (many open sockets per server); more complex to load-balance (sticky sessions or a routing layer); firewalls/proxies sometimes fight WebSocket upgrades; you own message ordering/reconnection semantics; scaling requires a message bus (Redis Pub/Sub / Kafka) between servers.
  • When to use: anything real-time and bi-directional — chat, multiplayer games, live collaboration (Figma, Google Docs), stock/crypto tickers, live betting.

9. Comparison matrix

Feature Short Poll Long Poll SSE WebSocket
Direction C↔S C↔S S→C only Full duplex
Real-time latency Poll interval ~ms ~ms ~ms
Transport HTTP HTTP HTTP HTTP → then TCP frames
Server resource per client Low (bursty) High (open conn) High (open conn) High (open conn)
Works with existing HTTP infra Mostly (some proxies fight it)
Auto-reconnect built-in Trivial Manual ✅ (EventSource) Manual
Overhead per message HTTP full HTTP full ~50 bytes text ~2–14 bytes framing
Message ordering Ordered per response Ordered per response Ordered Ordered per connection
Backpressure / flow control HTTP HTTP HTTP Manual (you handle)
Great for Rare updates Simple push Broadcasts, feeds Interactive, bi-di

10. Design gotchas at scale

10.1 Load-balancing WebSockets

  • WebSockets are long-lived, so you can't do simple round-robin per request. Once connected, that client is stuck to that server for the connection's lifetime.
  • Sticky sessions (based on client IP or a routing cookie) or a connection router (like a dedicated WebSocket gateway) is common.
  • L4 load balancers can pass WebSocket traffic through easily. L7 LBs need explicit WebSocket support (most do — HAProxy, nginx, AWS ALB).

10.2 Cross-server messaging

If a user is connected to server-A and another user (say, in the same chat room) is on server-B, server-A needs a way to tell server-B "deliver this message." Standard pattern:

Client A ─── WS ──▶ Server A ──▶ Pub/Sub (Redis / Kafka) ──▶ Server B ── WS ──▶ Client B

The pub/sub layer decouples app servers so any-to-any messaging works.

10.3 Connection storms after deploys

  • Redeploy a WebSocket server → 100k clients reconnect at once → thundering herd.
  • Mitigations: connection draining (existing conns finish; new ones go to new servers), staggered restarts, client-side reconnect with exponential backoff + jitter.

10.4 Memory / FD limits

  • Each open TCP connection uses kernel memory + a file descriptor.
  • Modern Linux can handle hundreds of thousands of concurrent conns per host with tuning (raise nofile ulimit, increase net.ipv4.tcp_mem, use epoll).
  • But you'll need a fleet — no single box holds a million WebSockets comfortably.

10.5 Mobile networks eat connections

  • Cell networks aggressively kill idle TCP connections. WebSockets and SSE need keepalive pings every ~30–60 s or the connection dies silently.
  • On mobile, push notifications (APNs/FCM) are often the right delivery channel, not WebSockets, because they use the OS-managed persistent connection.

11. Interview angle (what FAANG actually asks)

Common flavors:

  • "How would you build a chat system?" → WebSockets between clients and servers; Redis/Kafka pub-sub to fan out messages between servers; message store (Cassandra) for history. Discuss sticky sessions or a WebSocket gateway.
  • "How would you build live notifications for 100M users?" → SSE for web (or WebSockets), APNs/FCM for mobile background. Fan-out is the interesting problem (Day 18).
  • "When would you pick UDP over TCP?" → real-time media, DNS, gaming, telemetry, QUIC. Always tie it back to stale > missing semantics.
  • "How does HTTP/3 give better performance than HTTP/2?" → runs on UDP-based QUIC; multiplexes streams without HOL blocking; faster handshake (0-RTT possible on resume); handles connection migration across networks (great for mobile).
  • "Difference between SSE and WebSockets?" → SSE is one-way, HTTP-native, auto-reconnecting, simpler. WebSockets are full-duplex, lower overhead per message, better for interactive. Rule of thumb: if the client rarely talks back, SSE. If it's a conversation, WebSockets.

Trap they set: they ask "how would you build a stock ticker?" and if you say "polling" or "WebSockets" without asking "who's the client — web browser, mobile app, or another server?" — you've already lost points. Always clarify the client and the SLA (updates per second, acceptable staleness) before picking a mechanism.


12. Quick reference card (memorize)

  • TCP = reliable, ordered, connection-oriented. UDP = fast, unreliable, connectionless.
  • UDP wins when stale > missing. Media, gaming, DNS, telemetry.
  • TCP head-of-line blocking: one lost packet delays all later data on that connection.
  • QUIC/HTTP-3: solves HOL blocking by using UDP + per-stream reliability. Basis of HTTP/3.
  • Short poll / long poll / SSE / WebSockets — the four server-push mechanisms.
  • SSE = one-way (S→C), HTTP-native, auto-reconnecting. Use for feeds, notifications, LLM token streaming.
  • WebSockets = full-duplex, minimal overhead. Use for chat, gaming, collaboration.
  • At scale: WebSockets need sticky sessions/routing + pub-sub across servers. Mobile needs keepalive pings or push notifications.

Ready for the Day 3 quiz? Open it in the sidebar (quizzes/day-03.html). Answer in chat when done.