HomeProjectsBlogContact

WebSockets vs Server-Sent Events vs Polling: Real-Time Made Simple

Live notifications, chat, dashboards and progress bars all need real-time updates, but not all need WebSockets. How each approach works, the trade-offs, and which one fits your feature.

WebSockets vs Server-Sent Events vs Polling: Real-Time Made Simple

"We need real-time" usually means one of a few things: show a notification when something happens, update a dashboard as numbers change, show progress on a long task, or let users chat. Each can be built several ways, and reaching for WebSockets by default often adds complexity you don't need.

Option 1: polling

The client asks the server for updates on a timer.

setInterval(async () => {
  const res = await fetch('/api/notifications?since=' + lastSeen)
  render(await res.json())
}, 15000)
  • Pros: dead simple, works everywhere, plays nicely with caches, load balancers and serverless.
  • Cons: updates are delayed by up to the interval, and most requests return "nothing new," which wastes resources at scale.

Good for: dashboards that refresh every few seconds or minutes, checking a job's status, low-traffic internal tools. Honestly, polling covers more use cases than people admit.

Long polling is a variation: the server holds the request open until there's something new (or a timeout), then the client immediately asks again. It reduces delay, but has mostly been replaced by SSE.

Option 2: Server-Sent Events (SSE)

The server keeps one HTTP response open and pushes events down it. The browser API is built in:

const events = new EventSource('/api/stream')
events.addEventListener('order_update', e => {
  render(JSON.parse(e.data))
})

On the server, you send a response with Content-Type: text/event-stream and write lines like:

event: order_update
data: {"id": 991, "status": "shipped"}
  • Pros: uses plain HTTP, reconnects automatically, supports event IDs so clients can resume, simple on both ends.
  • Cons: one direction only (server to client). The client uses normal requests to send data. Text-based.

Good for: notifications, live feeds, progress updates, streaming AI responses token by token, live scores and prices. Many "real-time" features are really server-to-client only, which makes SSE a great fit.

Option 3: WebSockets

A persistent, two-way connection. Either side can send messages at any time.

const socket = new WebSocket('wss://example.com/ws')
socket.addEventListener('message', e => render(JSON.parse(e.data)))
socket.send(JSON.stringify({ type: 'chat', text: 'Hello' }))
  • Pros: true bidirectional, low-latency messaging; supports binary data.
  • Cons: more infrastructure work: connection state, reconnection logic, heartbeats, scaling across servers, proxy configuration. No automatic reconnection; you build it or use a library.

Good for: chat, multiplayer games, collaborative editing, live cursors, anything where clients send frequent messages too.

Comparison

Polling SSE WebSockets
Direction Client asks Server → client Both ways
Latency Up to the interval Low Lowest
Complexity Very low Low Higher
Auto-reconnect N/A Built in Build it yourself
Works through typical proxies Yes Yes (disable buffering) Needs upgrade config
Serverless friendly Yes Depends on platform Usually needs special support

Infrastructure gotchas

  • Proxies. Nginx needs upgrade headers for WebSockets, and buffering turned off for SSE (proxy_buffering off; or the X-Accel-Buffering: no header). Long-lived connections need longer timeouts. (Nginx reverse proxy config.)
  • Multiple servers. If a user connects to server A but the event happens on server B, server A needs to hear about it. A pub/sub layer (Redis pub/sub, Postgres LISTEN/NOTIFY, or a message broker) fans events out to every server. (Scaling.)
  • Serverless platforms often limit how long a function can hold a connection open. Check your platform's support before designing around long-lived connections.
  • Authentication. Authenticate when the connection opens (cookies work for SSE and WebSockets on the same domain) and re-check permissions for what each user may receive. (Security checklist.)

Managed services

If you'd rather not run real-time infrastructure yourself, managed services handle connections, scaling and fan-out for you. Supabase Realtime, Firebase, Pusher, Ably and similar products let your backend publish events while they manage the sockets. Great for small teams.

How to choose

  1. Do updates only flow from server to client? Use SSE.
  2. Is a delay of several seconds acceptable? Use polling. Seriously.
  3. Do clients send frequent messages too (chat, games, collaboration)? Use WebSockets, or a managed service.
  4. On serverless with no long-lived connection support? Polling or a managed real-time service.

Start with the simplest option that meets the requirement. You can upgrade later without changing what users see.

FAQ

When should I use WebSockets instead of SSE?

When the client also needs to send frequent messages over the same connection, like chat, multiplayer games or collaborative editing. For server-to-client updates only, SSE is simpler.

Is polling bad?

No. Polling is simple and reliable, and perfectly fine for updates that can be a few seconds late. It becomes inefficient only with very short intervals and many clients.

Do Server-Sent Events work with HTTP/2?

Yes, and HTTP/2 removes the old per-domain connection limit that made many SSE streams awkward over HTTP/1.1.

Keep reading

More from the workbench

All articles →