WebSockets vs Polling vs Server-Sent Events

Difficulty: Intermediate

Question

Compare short polling, long polling, Server-Sent Events and WebSockets. How would you build a live cricket score or chat feature and which would you pick?

Answer

Plain HTTP is a request-response protocol: the client always speaks first. That is a problem the moment the server has news, like a new chat message or a wicket falling, because it has no legitimate way to push it. Over the years we built four techniques to fake or provide real-time updates, each with a different cost profile.

Short polling is the naive approach: the client asks every few seconds, is there anything new. It is trivial to build and works everywhere through any proxy, but it wastes bandwidth and server capacity because most responses are empty, and latency is up to the polling interval. If 100,000 users poll every 2 seconds, that is 50,000 requests per second mostly returning nothing.

Long polling improves it: the client sends a request and the server holds it open until there is data (or a timeout, say 30 seconds), then responds; the client immediately reconnects. Latency is low and it works over plain HTTP, but every event still costs a full request with headers, a connection is held per user, and it is awkward to manage ordering and missed events between reconnects. Facebook chat and early Gmail used variations of it.

Server-Sent Events (SSE) use a single long-lived HTTP response with the content type text/event-stream. The server keeps writing lines of data as events happen. In the browser you use the EventSource API, which gives you automatic reconnection and a Last-Event-ID header for resuming. It is one-directional, server to client only, text only, and is plain HTTP so it works nicely with existing proxies, authentication and HTTP/2 multiplexing. It is perfect for live scores, notifications, stock tickers, and streaming LLM responses. Under HTTP/1.1 browsers cap around 6 connections per domain, a limit removed in practice by HTTP/2.

WebSocket is a different protocol that begins life as an HTTP request: the client sends a GET with Upgrade: websocket and Sec-WebSocket-Key headers, the server replies with 101 Switching Protocols, and the same TCP connection is now a persistent, full-duplex channel with small binary or text frames (as little as 2 bytes of overhead). Both sides can send at any time. It suits chat, multiplayer games, collaborative editing and trading dashboards. The cost: it is stateful, so scaling is harder. Load balancers must support the upgrade and long-lived connections, servers hold one connection per user (memory and file descriptors), and to broadcast across multiple servers you need a pub/sub backbone such as Redis, Kafka or NATS. You also need your own heartbeat (ping/pong), reconnection, backoff and auth logic, and WebSocket does not automatically get HTTP caching or standard middleware.

So which should you pick? For a live cricket score, updates flow one way from server to client and happen every few seconds, so SSE is the simplest robust choice; polling every 5 to 10 seconds is acceptable for a small audience or when infrastructure is restricted. For chat, where clients also send messages with low latency and presence matters, WebSocket is the natural fit. Long polling is the fallback for environments where neither works. Libraries like Socket.IO wrap these and fall back automatically. In interviews, always talk about scale: connection count per node, sticky routing, back-pressure for slow clients, and how you fan out a message to a million subscribers.

Code examples

WebSocket upgrade handshake and a Node SSE endpoint

GET /chat HTTP/1.1
Host: app.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

// Node SSE
app.get('/score', (req, res) => {
  res.set({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' });
  const t = setInterval(() => res.write('data: ' + JSON.stringify({ runs: 187 }) + '\n\n'), 5000);
  req.on('close', () => clearInterval(t));
});

The 101 response ends HTTP semantics on that connection. SSE just keeps writing to an open response.

Key points

Concepts covered

WebSocket, Long Polling, Server-Sent Events, Real-Time, Scaling