NodeJSReal-Time Apps Overview

Real-Time Apps Overview

A real-time application pushes data to clients the instant it changes, rather than making them ask repeatedly. Chat, live dashboards, collaborative editors, multiplayer games, notifications, live sports scores — all need the server to initiate communication, which ordinary request/response HTTP can't do. Node is exceptionally well-suited to this: its event-driven, non-blocking model lets a single process hold tens of thousands of open connections cheaply. This page frames the problem, compares the techniques (polling, long polling, SSE, WebSockets), and helps you pick the right one — before later pages dive into WebSockets, Socket.IO, and SSE.

Why plain HTTP isn't enough
HTTP is client-initiated request/response — the server can't push; real-time needs a different model
Classic HTTP is **client-initiated**: the client asks, the server answers, the connection closes. The server has no way to say "something just happened" on its own — it can only respond to requests. For real-time features the data flow is the other way around: the *server* needs to notify clients the moment an event occurs (a new chat message, a price tick, another user's edit). Bridging this gap requires either repeatedly asking (polling) or keeping a connection open so the server can send whenever it likes (SSE, WebSockets). Node's ability to hold many idle-but-open connections without burning a thread each is precisely why it became the go-to runtime for real-time backends.
The techniques compared

Technique

Direction

How it works

Best for

Short polling

Client pulls

Client requests on a timer (every N sec)

Simple, infrequent updates; legacy support

Long polling

Client pulls (held)

Server holds the request open until data, then client re-asks

Fallback when WS/SSE unavailable

SSE

Server → client (one-way)

One long-lived HTTP stream, server pushes events

Server-to-client feeds: notifications, dashboards

WebSockets

Bidirectional

Full-duplex persistent TCP connection

Two-way, low-latency: chat, games, collaboration

The progression: polling (ask repeatedly) → long polling (held request) → SSE (one-way push) → WebSockets (two-way)
These techniques form a progression of increasing capability. **Short polling** — the client asks every few seconds — is trivial but wasteful (most requests return "nothing new") and laggy (updates arrive up to one interval late). **Long polling** improves latency by having the server *hold* the request open until there's data, then the client immediately re-requests — better, but still one request per message and awkward. **SSE** opens a single long-lived stream over which the server pushes events as they happen — efficient and simple, but **one-way** (server→client only). **WebSockets** upgrade the connection to a persistent, **bidirectional** channel where either side can send at any time — the most capable, and the right choice for truly interactive apps.
Polling — simple but costly

JS
// Short polling — the client asks on a fixed timer regardless of whether anything changed:
setInterval(async () => {
  const res = await fetch('/api/messages?since=' + lastId)
  const messages = await res.json()
  if (messages.length) render(messages)     // usually [] — a wasted round-trip
}, 3000)   // 3s latency on updates, constant traffic even when idle
Polling wastes bandwidth and adds latency — most requests return nothing, and updates lag by the interval
Short polling has two structural problems. **Waste**: the client requests on a fixed schedule whether or not anything changed, so the vast majority of requests return empty — multiply that by thousands of clients and you've built a self-inflicted load problem with full HTTP overhead (headers, TLS, auth) on every empty poll. **Latency**: an update that arrives just after a poll waits the entire interval before the client sees it. Shortening the interval reduces latency but multiplies the waste. Polling is acceptable only for low-frequency, non-urgent updates or as a last-resort fallback — for anything genuinely real-time, use SSE or WebSockets, which push only when there's actually something to send.
Choosing the right technique
  • Server → client only? (notifications, live feeds, dashboards, progress) → SSE — simpler than WebSockets, runs over plain HTTP, auto-reconnects.

  • Bidirectional & low-latency? (chat, multiplayer, collaborative editing) → WebSockets.

  • Want fallbacks, rooms, and reconnection handled for you?Socket.IO (a library on top of WebSockets).

  • Infrequent updates, maximum simplicity, no infra changes? → polling may be fine.

  • Behind restrictive proxies/firewalls? → SSE/long-polling often pass more reliably than raw WebSockets.

Don't reach for WebSockets by default — if data only flows server→client, SSE is simpler and often better
A common over-engineering trap is choosing WebSockets for everything. If your data only flows **one way** — server to client, like a notification feed, a live dashboard, or progress updates — [SSE](/nodejs/server-sent-events) is the better fit: it's plain HTTP (works through most proxies, no protocol upgrade), the browser's `EventSource` reconnects automatically, and there's far less to manage. Save [WebSockets](/nodejs/websockets) for genuinely **bidirectional, interactive** use cases (chat, games, collaboration) where the client also sends frequently and latency must be minimal. Match the tool to the actual data-flow direction rather than defaulting to the most powerful option.
What real-time at scale demands
Persistent connections are stateful — scaling them across multiple instances needs a pub/sub backplane
Real-time introduces a scaling challenge ordinary HTTP doesn't have: connections are **long-lived and stateful**, so a client is bound to whichever instance it connected to. When you run multiple instances ([horizontal scaling](/nodejs/scaling-strategies)), a message published on instance A must reach clients connected to instances B and C — but those instances don't share memory. The solution is a **pub/sub backplane** (commonly [Redis](/nodejs/redis)): each instance publishes events to Redis and subscribes to relevant channels, so a message fan-outs to every connected client regardless of which instance holds their connection. You also need to budget memory per connection, handle reconnection and missed-message recovery, and configure load balancers and proxies for long-lived connections (sticky routing or proper WebSocket support).
Next
The full-duplex workhorse of real-time: [WebSockets (ws)](/nodejs/websockets).