Communication Protocols (HTTP Polling, Long Polling, WebSockets, SSE, gRPC)
The Question Behind the Question
"How does the client get real-time updates?" is really "what's the trade-off between connection overhead, latency, and infrastructure complexity you're willing to accept?" Every chat/notification/live-feed problem hinges on this choice.
Short (HTTP) Polling
Client asks "anything new?" on a fixed interval, regardless of whether there's anything new.
import time, requests
def poll(url, interval_s=3):
while True:
resp = requests.get(url)
if resp.json().get("has_update"):
handle(resp.json())
time.sleep(interval_s)
Cons: wastes requests when nothing changed, and update latency is bounded by the interval (average delay = interval/2). Pros: trivial to implement, works everywhere, no persistent connection to manage.
Long Polling
Client asks "anything new?" — server holds the request open until there's an update (or a timeout), then responds; client immediately re-requests.
import requests
def long_poll(url, timeout_s=30):
while True:
resp = requests.get(url, params={"timeout": timeout_s})
if resp.status_code == 200:
handle(resp.json())
# else: timed out with no update, just re-request
Pros: much lower latency than short polling, no new protocol needed (still plain HTTP). Cons: server must hold open connections/threads for every waiting client — resource cost scales with concurrent idle clients, a real capacity concern at scale.
WebSockets
A single TCP connection is upgraded from HTTP to a persistent, full-duplex channel — either side can push a message at any time, no request/response ceremony per message.
import asyncio, websockets
async def handler(websocket):
async for message in websocket:
await websocket.send(f"echo: {message}") # push anytime, either direction
async def main():
async with websockets.serve(handler, "localhost", 8765):
await asyncio.Future() # run forever
Pros: lowest latency, true bidirectional push — the right choice for chat, live collaborative editing, multiplayer state sync. Cons: stateful connection per client (doesn't fit cleanly behind simple round-robin load balancers — needs sticky routing or a connection-aware gateway), harder to scale horizontally (a message to a user must be routed to whichever server holds their socket — typically solved with a pub/sub layer like Redis pub/sub or Kafka fanning out to the right server).
Server-Sent Events (SSE)
One-directional push, server → client only, over plain HTTP (no upgrade needed).
Pros: simpler than WebSockets when the client never needs to send data back over the same channel (live score updates, notification streams) — auto-reconnect built into the browser EventSource API. Cons: one-directional only; client writes still need a separate normal HTTP request.
gRPC
RPC framework over HTTP/2, with strongly-typed contracts (Protobuf) and support for streaming (client, server, or bidirectional).
Pros: efficient binary serialization, multiplexed streams over one connection, strong typing catches contract mismatches at compile time — the default choice for service-to-service internal communication at scale. Cons: not natively browser-friendly (needs gRPC-Web + a proxy translation layer), less human-debuggable than JSON over REST.
Decision Table
| Need | Choice |
|---|---|
| Occasional updates, simplicity over efficiency | Short polling |
| Lower latency, no new infra, moderate concurrency | Long polling |
| True real-time, bidirectional, high concurrency | WebSockets (+ pub/sub fanout) |
| Server → client stream only, want auto-reconnect | SSE |
| Internal service-to-service, high throughput, typed contracts | gRPC |
Interview Drill
Q: You're designing WhatsApp-style chat. A user has the app open on 3 devices. A message arrives. Walk through delivery. A: The sending client's message hits the chat service, which persists it, then needs to push to all 3 of the recipient's active WebSocket connections — which may be held by 3 different server instances (connections aren't sticky to one device necessarily, but each device does hold its own persistent socket). Use a pub/sub layer (Redis pub/sub or a Kafka topic keyed by user_id) so any server instance that publishes "new message for user X" gets fanned out to whichever instances currently hold that user's live sockets. This is the concrete reason WebSockets don't scale horizontally "for free" the way stateless HTTP does — you need this extra fanout layer.
Cross-links
[[Load Balancing]] · [[Message Queues & Kafka]] · [[API Design Principles]]