4 Ways to Get Real-Time Updates: Polling to WebSockets
Live UIs need the server to push changes — stock ticks, chat lines, build progress, presence. Classic HTTP is pull-only: the client asks, the server answers, the connection closes. Real-time patterns bend that model in four common ways: short polling, long polling, Server-Sent Events (SSE), and WebSockets.
The difference is not marketing labels — it is when requests fire, who can send next, and how much idle traffic you pay for. Place these options on a delivery timeline here; put protocol layering for microservices in Where Each Protocol Belongs.
Four patterns on a timeline
Picture client and server as two vertical lanes. Time runs downward. Short polling fills the page with tiny GET/empty pairs. Long polling draws one long wait, then a reply, then another GET. SSE opens one stream and stacks server→client events. WebSockets show a short upgrade handshake, then arrows in both directions.
Use that mental model when someone says “we need real-time.” Ask: do we need the client to push on the same channel, or only the server? How often do empty checks waste capacity? Is a corporate proxy going to block upgrades?
Quick reference
- Short polling: many requests, often zero useful updates.
- Long polling: one held request per update (or timeout).
- SSE: one HTTP connection, many server events.
- WebSocket: one handshake, then bidirectional frames.
- Same product can mix patterns — e.g. REST write + SSE read.
Remember this
Each pattern trades latency against connection cost differently — the right one depends on how fresh the client actually needs to be.
Short polling
Short polling is the blunt instrument: the client asks on a timer — every 1s, 5s, 30s — whether anything changed. The server answers immediately, usually with “nothing new.” It works anywhere HTTP works and needs no special protocol support.
The cost is obvious on a busy dashboard: thousands of clients × frequent ticks = mostly empty responses, load-balancer noise, and battery drain on mobile. Prefer it only for low-frequency status (“is the job done yet?” every few seconds) or when you cannot open a held connection.
Quick reference
- Best for: rare status checks, firewalled environments, tiny prototypes.
- Strengths: trivial to implement; cacheable GETs if designed carefully.
- Weaknesses: wasteful empty replies; lag equals your poll interval.
- Backoff when idle; poll faster only after a user action.
- Prefer
ETag/If-None-Matchto keep empty responses cheap. - Skip when update rate is high or latency must beat the timer.
Remember this
Most short-polling requests return nothing new — that empty-response waste is the price of its simplicity.
Long polling
Long polling keeps the request open until the server has something — or a timeout fires. When the response finally arrives, the client immediately opens the next request. You still speak plain HTTP, but empty chatter drops because the server does not answer until there is news.
It is the classic Comet-style fallback when WebSockets or SSE are blocked. The trade-off: each update still costs a full request/response, and held connections tie up worker capacity if your stack is thread-per-request. Set a server timeout (often 30–60s), handle 504s gracefully, and reconnect with jitter.
Quick reference
- Best for: legacy proxies, maximum HTTP compatibility, temporary bridges.
- Strengths: fewer empty replies than short polling; works through strict firewalls.
- Weaknesses: still request-per-event; holding connections stresses some servers.
- Client must re-request immediately after every response or timeout.
- Watch connection pools and reverse-proxy idle timeouts.
- Graduate to SSE or WebSocket when infrastructure allows.
Remember this
Long polling holds the GET open until data arrives or a timeout fires — cutting empty responses at the cost of one held connection per waiting client.
Server-Sent Events (SSE)
SSE uses one HTTP response with Content-Type: text/event-stream. After the initial GET, the server writes data: lines whenever events happen. Browsers expose this as EventSource, which reconnects automatically and can resume with Last-Event-ID.
Direction is server→client only — perfect for notifications, live feeds, progress bars, and LLM token streams. Clients still use normal REST/POST for actions. SSE rides standard HTTP (and HTTP/2 multiplexing), so load balancers and CDNs usually need less special casing than raw WebSockets.
Quick reference
- Best for: feeds, notifications, progress, dashboards, one-way streams.
- Strengths: simple HTTP; auto-reconnect; proxy-friendly; text events.
- Weaknesses: not bidirectional; text-oriented; browser per-host connection limits.
- Auth on the opening GET (cookie or query/header patterns your stack supports).
- Send heartbeats/comments to keep intermediaries from closing idle streams.
- Pair with REST writes: POST to mutate, SSE to observe.
Remember this
SSE only needs a one-way HTTP stream, so it skips the WebSocket handshake entirely when the client never needs to push back.
WebSockets
WebSocket starts with an HTTP Upgrade handshake. On success (101 Switching Protocols), both sides exchange frames anytime — chat, multiplayer, collaborative cursors, trading UIs. Latency stays low because you skip HTTP headers on every message.
Stateful connections make horizontal scale harder: use sticky sessions or a pub/sub fabric (Redis, NATS) so any instance can fan out. Prefer battle-tested libraries (Socket.IO, ws, SignalR) with heartbeats, backoff reconnect, and message replay. Test through your real load balancer — some strip upgrade headers. Edge upgrade config belongs in NGINX Gateway.
Quick reference
- Best for: chat, games, collab editing, live trading — any true duplex need.
- Strengths: lowest chatty latency; binary frames; either side can speak.
- Weaknesses: sticky/pub-sub ops; reconnect logic; some proxies fight upgrades.
- Implement ping/pong and exponential backoff with replay.
- Consider managed realtime (Pusher, Ably) if connection ops are not your product.
- Do not default to WebSocket for one-way notification feeds — SSE is simpler.
Remember this
A WebSocket connection is stateful and pinned to one server process — scaling past one instance means sticky routing or a pub-sub fan-out, not just adding replicas.
Choosing the right pattern
Decision order: need client→server on the same channel? WebSocket. Server push only? SSE. Those blocked by infra? Long polling. Only rare status checks and you want zero protocol novelty? Short polling.
In microservices, produce events once (Kafka, queue, DB change feed) and let a gateway translate to SSE or WebSocket for browsers. For Next.js-style apps, stream SSE from route handlers for one-way updates; put duplex sockets on a dedicated process or managed service. Measure connection count, message rate, reconnect storms, and p99 delivery lag — not just “is it live?”
Quick reference
- Bidirectional → WebSocket · server push → SSE · blocked → long poll · rare → short poll.
- SSE + REST covers most product notifications without socket ops.
- Kafka → realtime gateway → clients decouples producers from browsers.
- Validate through production LB/CDN before betting on WebSocket.
- Practice: for a “build finished” toast, implement SSE first; only add sockets if the UI must push on the same pipe.
Remember this
Directionality (one-way vs bidirectional) and infrastructure constraints — not personal preference — decide which of the four patterns fits.
Key takeaway
Real-time is a ladder of cost and capability: short poll (simple, wasteful), long poll (held HTTP), SSE (one-way stream), WebSocket (full duplex). Start from the timeline — who speaks, how often, and what your proxies allow — then pick the lightest pattern that meets the UX. Default to SSE for push feeds and WebSockets when both sides must talk; keep polling as a conscious fallback, not a habit.
Practice (20 min): Sketch client/server lanes for a live order-status page, then implement the SSE sample above (three data: events + heartbeat). Kill the server mid-stream and confirm the client shows a reconnect path. Only if the UI must push on the same pipe, swap in the WebSocket backoff sketch.
Related Articles
Explore this topic