Back to Networking
Networking
medium
mid

When should you use WebSockets, Server Sent Events, or polling?

Polling is simplest (HTTP). SSE is one-way server→client (text, auto-reconnect, HTTP/2-friendly). WebSockets are bidirectional, binary-capable, lowest-latency. Pick the *least* powerful that fits — operational cost rises with each.

7 min read·~18 min to think through

Three transports for "the server has new data":

Polling — client sends GET /events every N seconds. Pros: trivial, works through every proxy, idempotent. Cons: latency (worst case = N seconds), wasted requests, hard to scale below ~5s.

Long polling — server holds the request open until data arrives, then returns. Latency is near-instant. Cons: complex to operate at scale, proxy timeouts, one connection per client.

Server-Sent Events (SSE) — server streams text events over an HTTP response. Pros: native EventSource API, automatic reconnect with Last-Event-ID, plays nicely with HTTP/2 multiplexing, simple to debug. Cons: one-way (server → client only), text only, no IE.

WebSockets — full-duplex, binary-capable. Pros: lowest latency in both directions, low overhead per message. Cons: requires upgrade negotiation, separate scaling/load-balancing concerns, more error states (connection drops, message ordering on reconnect), often need a separate sticky-session infra.

Decision flow:

  • Need to send messages to the server in real time? → WebSocket.
  • One-way push from server is enough? → SSE.
  • Updates are infrequent (≥30s) or you can't operate stateful infra? → Polling.

Modern alternatives often used: WebTransport (HTTP/3), GraphQL subscriptions over WebSocket, libraries like Pusher/Ably/Liveblocks that abstract the transport.

Code

ts
const es = new EventSource("/api/feed");
es.onmessage = (e) => console.log("event:", e.data);
es.onerror = () => {/* EventSource auto-retries */ };
// Server emits: `data: {"id":42}\n\n`
SSE on the client
ts
const ws = new WebSocket("wss://api.example.com/live");
ws.onopen    = () => ws.send(JSON.stringify({ subscribe: "orders" }));
ws.onmessage = (e) => apply(JSON.parse(e.data));
ws.onclose   = (e) => reconnectWithBackoff(e.code);
WebSocket — bidirectional

Follow-up questions

  • How do you scale WebSockets across multiple servers?
  • When does HTTP/2 multiplexing remove SSE's connection-limit problem?
  • How do you handle ordering and dedupe across reconnects?

Common mistakes

  • Defaulting to WebSockets for read-only updates that SSE handles cheaper.
  • Not handling reconnect / backoff — temporary network blips kill the stream forever.
  • Forgetting to authenticate the upgrade — an unauthenticated WS endpoint is a foot-gun.

Performance considerations

  • Connection counts: polling = N clients × 1 short request per cycle. WebSocket = N persistent connections — server memory and load-balancer config matter.

Edge cases

  • Corporate proxies sometimes break WebSocket upgrades but allow SSE.
  • Browsers cap concurrent SSE connections per origin (~6 over HTTP/1) — HTTP/2 fixes this.

Real-world examples

  • Stripe Dashboard uses SSE for activity feeds; Figma and Linear use WebSockets for collaborative editing.

Senior engineer discussion

Senior signal: discuss state synchronization (operational transforms, CRDTs), backpressure, sticky sessions vs Redis pub/sub, and graceful degradation to polling.