← Back to all articles

WebSocket vs Polling: Choosing Real-Time Communication

NetworkBeginner

Three approaches

ApproachMechanismCost
Short pollingPoll on a timer for new dataWasted requests, high latency
Long pollingHold the request until data arrivesRebuilds each time, server holds connections
WebSocketFull-duplex long connection after one handshakeConnection needs upkeep, stateful

How to choose

  • Low frequency, tolerant delay: short polling is simplest;
  • Near-real-time, one-way push: long polling suffices and is proxy-friendly;
  • High-frequency two-way, chat or quotes: WebSocket fits best.

WebSocket notes

  1. Heartbeat: send pings or a proxy may drop the idle connection;
  2. Reconnect: on flaky networks, reconnect with exponential backoff to avoid hammering the server;
  3. Scale out: with multiple instances, a message bus broadcasts to the node holding that connection.

Real-world cases: three "it connected but is unstable"

  1. Everyone drops after a few minutes: Nginx's default proxy_read_timeout is 60 seconds, so idle long connections are cut. Fix: raise the timeout and have the client send heartbeats.
  2. A reconnect storm takes down the server: after a network outage every client reconnects at once. Fix: exponential backoff with jitter, plus a per-IP connection rate limit.
  3. Messages lost across instances: the user is connected to instance A but the message is produced on instance B, which holds no such connection. Fix: broadcast via Redis Pub/Sub or a message bus, or route consistently by user.

FAQ

Can WebSocket send auth headers? Native browser WebSocket cannot set custom headers; the usual pattern is a one-time short-lived token in the handshake URL, or cookie validation. Are messages during a disconnect replayed? No — a long connection does not guarantee delivery; use sequence numbers plus a history fetch to catch up. Can SSE replace it? If only the server pushes, SSE is simpler: automatic reconnect over plain HTTP. Is WebSocket always cheaper than polling? Only when connections are stable and messages frequent; for low-frequency cases keeping long connections open is more expensive.

Sizing long connections

The bottleneck for WebSocket is rarely bandwidth; it is connection count and memory:

  • Memory per connection: tens to a hundred KB each including buffers and runtime overhead, so a process holding tens of thousands needs that budgeted;
  • File descriptors: one per connection — raise both process and system ulimit or hit the ceiling under load;
  • Heartbeat amplification: at one ping per 30 seconds per connection, 100,000 connections means thousands of messages a second before any real traffic;
  • Stateful operations: rolling deploys must drain gracefully so clients reconnect on schedule, and scaling needs connection migration;
  • Broadcast cost: one fan-out to every connection multiplies egress instantly; group subscriptions and push only to relevant connections.

The usual conclusion: keep long connections for high-frequency, genuinely bidirectional flows, and use SSE or a push service for low-frequency notifications.

Client-side implementation notes

  1. Reconnect with state recovery: reconnecting is step one; catch up on messages missed during the outage by sequence or timestamp, or users see discontinuous data.
  2. Distinguish offline from empty: surface the connection state instead of showing "no data" during an outage, which misleads users.
  3. Handle backgrounding: mobile systems may reclaim connections in the background, so check and rebuild on return rather than relying on automatic recovery.
  4. Bound message size: a large message blocks others, so send references and fetch on demand, or chunk the payload.
  5. Queue and throttle sends: on weak networks clients pile up sends; use a bounded send queue so reconnection does not flush a flood at once.

Most complexity lives in client state management. Separating connection state, data consistency and user experience makes the implementation much clearer.