WebSockets or Polling
20 Jul 2026Poll until you have a specific reason not to. WebSockets solve a real problem — low-latency, bidirectional communication — but they bring infrastructure costs that most “real-time” features don’t actually need.
Polling: boring and it works
The client asks for updates on an interval. That’s it.
setInterval(async () => {
const res = await fetch('/api/notifications?since=' + lastCheck)
render(await res.json())
}, 5000)
It runs over plain HTTP, so it works through every proxy, load balancer, and corporate firewall without configuration. It’s cacheable, it’s stateless on the server, and it scales the same way the rest of your API scales — add more app servers behind the load balancer, nothing changes. The cost is latency (up to one poll interval of delay) and wasted requests when nothing changed.
For most “real-time” dashboards, a 5-15 second poll interval is indistinguishable from real-time to the user, and it’s a fraction of the engineering cost.
Long polling: the middle ground
The client makes a request, and the server holds it open until there’s data to send or a timeout hits, then the client immediately reopens it. You get near-instant updates without maintaining a persistent connection protocol — it’s still plain HTTP requests, just longer-lived ones.
The cost is that you’re now holding open connections on the server, which matters if your app server has a low connection limit or you’re paying per connection-second on serverless. It also complicates load balancer timeout configuration — a 30-second long poll needs a load balancer timeout longer than 30 seconds, which is an easy thing to misconfigure and forget about until it silently drops connections in production.
WebSockets: full bidirectional, full cost
A WebSocket gives you a persistent, full-duplex connection — either side can push at any time with no request/response overhead. This is the right tool for chat, collaborative editing, multiplayer state, anything where the client also needs to send frequent low-latency updates, not just receive them.
What it costs you in infrastructure:
- Sticky sessions. A client’s WebSocket connection lives on one server process. If you scale horizontally, your load balancer needs to route reconnects back to a server that knows about that connection, or you need a shared pub/sub layer (Redis, NATS) so any server can broadcast to any connected client.
- Reconnect logic. Networks drop. Your client needs exponential backoff, and your server needs a way to replay or resync state the client missed while disconnected — WebSockets don’t give you this for free.
- No HTTP caching or CDN in front. Every connection is a stateful thing your infrastructure has to track, not a cacheable request.
- Harder to debug. You can’t curl a WebSocket. You need browser devtools or a dedicated client to inspect what’s actually being sent.
The decision
Which one to reach for comes down to two questions: does the client need to push data back, and does it need to happen instantly?
flowchart TD
A["Need live updates on the client?"] --> B{"Client also sends frequent, low-latency updates back?"}
B -->|"Yes"| C["WebSockets"]
B -->|"No"| D{"Is a 5-15s delay acceptable?"}
D -->|"Yes"| E["Polling"]
D -->|"No"| F["Server-Sent Events"]
| Polling | Long polling | WebSockets | |
|---|---|---|---|
| Latency | Poll interval | Near-instant | Instant |
| Server complexity | None | Low | High (sticky sessions, pub/sub) |
| Bidirectional | No | No | Yes |
| Works everywhere | Yes | Mostly | Needs fallback for some proxies |
If updates only flow server-to-client — notifications, status changes, a live counter — look at Server-Sent Events before WebSockets; it’s a simpler protocol built exactly for that case. Reach for WebSockets only when the client needs to send frequent low-latency messages back, like a chat app or a live cursor. Everything else, poll every 5-15 seconds and spend the engineering time you saved on the feature itself.