Part 5 · APIs & Communication
Real-Time Apps: Polling, WebSockets and SSE
HTTP only lets the client ask. For “new message”, “rider is nearby” or an AI answer streaming in, the server has to push. Short polling, long polling, Server-Sent Events and WebSockets compared — and how to scale millions of open connections.
On this page
The Problem
HTTP works like this: the client asks, the server answers. The server cannot start a conversation. But many features need the server to tell the client something the moment it happens. This is called a "push" (the server pushes data to the client). Examples:
- "You have a new message."
- "Your food is out for delivery."
- "The stock price just changed."
- "Your teammate edited this document."
- "The AI is still writing its answer…"
How do you send updates from server to client in real time, for millions of users, without overloading your servers? There are four main techniques: short polling, long polling, Server-Sent Events (SSE) and WebSockets. Each one fits different situations. "Real time" here means the update arrives within a moment, not minutes later.
The Core Idea
Imagine you are waiting for a parcel delivery.
- Short polling: you open the door every 5 minutes to check if it has arrived. It is simple, but most trips are wasted, and you might see the parcel up to 5 minutes late.
- Long polling: you call the courier and say "don't hang up until my parcel is here". When it arrives, they tell you, and you call again for the next one.
- Server-Sent Events (SSE): you subscribe to SMS updates from the courier. They message you every time something changes. It is one-way: you cannot reply by SMS.
- WebSockets: you and the courier keep a phone call open the whole time, and either of you can speak at any moment.
How It Works
1. Short polling
Short polling means the client sends a normal request again and again, every N seconds, to ask "anything new?":
- GET /notifications?since=…
- nothing new
- GET /notifications?since=…5 s later
- nothing new
- GET /notifications?since=…5 s later
- 1 new message!
setInterval(async () => { const res = await fetch("/api/notifications?since=" + lastSeen); render(await res.json());}, 5000);- ✅ Very simple. It works everywhere, with any backend, load balancer and CDN.
- ❌ Wasteful. Most requests return nothing.
- ❌ Delay. On average, updates arrive half an interval late.
Quick maths (lesson 10): 1 million users polling every 5 seconds = 200,000 requests per second, mostly returning "nothing new". That is a lot of server work that gives almost no value.
Good for: low-frequency updates, a small number of users, checking a background job's status ("Is my report ready?"), or dashboards refreshed every minute.
2. Long polling
With long polling, the client sends a request, and the server holds it open until there is news or a timeout (say 30 seconds). A timeout is a set time limit after which the server stops waiting. Then the client immediately sends another request.
- GET /updates
- ↻ hold… until news arrives
- 1 new message
- GET /updatesimmediately
- ↻ hold… 30 s, nothing
- timeout, nothing new
- GET /updates
- ✅ Near real-time, using plain HTTP, so it works through almost every proxy and firewall.
- ✅ Much less wasted traffic than short polling.
- ❌ Each waiting client holds a request open on the server. With thread-per-request servers (lesson 4), this means one thread per waiting user, which is costly. Event-loop servers (Node.js, NGINX) and servers with very light threads (Go) handle this much better.
- ❌ There is still extra work from reconnecting after every message. You also need care so that messages stay in order and none are missed.
Good for: a fallback when WebSockets are not possible, and moderate real-time needs. Early versions of many chat apps used long polling.
3. Server-Sent Events (SSE)
SSE is a standard way for a server to send a stream of events to a browser. The client opens one long-lived HTTP connection, and the server streams events down it whenever it wants. It is one-way: server to client.
GET /eventsAccept: text/event-streamHTTP/1.1 200 OKContent-Type: text/event-streamid: 101event: order_statusdata: {"orderId": 5521, "status": "out_for_delivery"}id: 102event: order_statusdata: {"orderId": 5521, "status": "delivered"}In the browser, you only need a few lines. EventSource is the built-in browser class for SSE:
const events = new EventSource("/events");events.addEventListener("order_status", (e) => { const update = JSON.parse(e.data); showStatus(update.status);});- ✅ Simple. It is plain HTTP with a text format. It works with normal load balancers, auth cookies and HTTP/2.
- ✅ Automatic reconnect built in. The browser reconnects and sends
Last-Event-ID, so the server can resume from where the client left off. - ✅ Great for streaming text.
- ❌ Server → client only. To send data up, the client uses normal HTTP requests (which is often perfectly fine).
- ❌ Text only (binary data must be encoded as text).
- ❌ The browser's
EventSourcecan only use GET and cannot set custom headers (such asAuthorization). Cookies work. Libraries that usefetchcan remove this limit. - ❌ With HTTP/1.1, browsers allow only about 6 connections per domain, and each SSE stream uses one. HTTP/2 multiplexing (many streams over one connection) removes this problem.
Good for: live feeds, notifications, sports scores, stock tickers, progress bars, deployment logs, and streaming AI responses word by word.
4. WebSockets
A WebSocket is a connection that stays open and lets both sides send messages at any time. It starts as an HTTP request that asks to upgrade the connection (switch to another protocol). Then it becomes a persistent, two-way channel:
- GET /chat · Upgrade: websocket
- 101 Switching Protocols
- Full duplex — either side may send a frame (one message unit) at any time
- {type: message, to: ravi, text: hi}
- {type: typing, from: ravi}
- {type: message, from: ravi, text: hello!}
- pingheartbeat every ~30 s
- pong
const ws = new WebSocket("wss://chat.example.com/ws");ws.onmessage = (e) => handle(JSON.parse(e.data));ws.send(JSON.stringify({ type: "message", to: "ravi", text: "hi" }));- ✅ True two-way, low-latency communication, with very little overhead per message after setup.
- ✅ Supports binary data (games, audio).
- ❌ Stateful connections. "Stateful" means the server must remember each open connection. This makes scaling, deploys and load balancing harder (see below).
- ❌ Some proxies and corporate firewalls interfere with long-lived connections. Use
wss://(WebSocket over TLS, the encrypted version), which usually gets through. - ❌ No built-in reconnect or resume. You must build heartbeats, reconnection and message recovery yourself (or use a library such as Socket.IO, which adds these features).
Good for: chat, multiplayer games, collaborative editors, live trading screens, and anything where both sides send frequent messages.
Comparison
| Short polling | Long polling | SSE | WebSockets | |
|---|---|---|---|---|
| Direction | Client asks | Client asks, server delays | Server → client | Both ways |
| Latency | Up to the interval | Low | Low | Lowest |
| Server cost | High (many empty requests) | Medium (held requests) | Low–medium (open streams) | Low–medium (open connections) |
| Protocol | Plain HTTP | Plain HTTP | Plain HTTP | Upgraded protocol |
| Auto-reconnect | N/A | Manual | ✅ Built in | ❌ Build it yourself |
| Binary data | ✅ | ✅ | ❌ (text) | ✅ |
| Best for | Rare updates, job status | Fallback, moderate real-time | Feeds, notifications, AI streaming | Chat, games, collaboration |
Scaling persistent connections
With SSE and WebSockets, each user keeps a connection open to one specific server. This creates new design problems.
1. Connection capacity. A well-tuned server can hold tens or even hundreds of thousands of mostly idle connections. But you must tune the file-descriptor limit (the number of open connections and files the operating system allows), the memory per connection, and the timeouts. Estimate it: 10 million online users ÷ 100k connections per server = 100 gateway servers.
2. Routing messages to the right server. User A is connected to gateway 1. User B is connected to gateway 7. A gateway is a server that holds the open connections. When A sends B a message, how does it reach gateway 7?
- 1User A's connectiona WebSocket to gateway 1
- 2Gateway 1stores the message durably first, then publishes it
- 3Pub/Sub backplaneRedis, NATS or Kafka — every gateway subscribes
- 4Presence registry“user B is on gateway 7”
- 5Gateway 7pushes the frame down B's socket
- 6User Bsees it in milliseconds; on reconnect, replays anything missed by sequence number
The common pattern:
- Gateways only manage connections.
- A pub/sub system (publish/subscribe: senders publish messages and subscribers receive them; for example Redis Pub/Sub, NATS, Kafka) passes messages between gateways.
- A presence registry records which user is connected to which gateway.
We'll design this fully in the chat-system case study (lesson 63).
3. Load balancing. Balancing connections evenly matters more than balancing requests. Use least-connections balancing (send new connections to the server with the fewest). Remember that a new server starts empty. Long-lived connections do not move to it on their own.
4. Deploys and restarts. Restarting a gateway drops every connection on it. Clients must reconnect with backoff and random jitter. Backoff means waiting longer after each failed try. Jitter means adding a random delay. Without them, thousands of clients reconnecting at the same instant will create a stampede (Part 7). Drain connections gradually during deploys, which means closing them a few at a time.
5. Heartbeats. A heartbeat is a small message sent again and again to show the connection is alive. Send ping/pong messages every 20–60 seconds. They detect dead connections (like a phone that went into a tunnel), and they stop proxies from closing "idle" connections.
6. Do not lose messages. Connections will drop. So:
- give messages sequence numbers or IDs,
- on reconnect, the client says "the last thing I got was #1041", and the server replays what was missed from storage,
- store messages durably first, then push them. The push is a notification, not the only copy.
Beyond these four
- Webhooks: server-to-server push. You give another service a URL, and it calls that URL when something happens, for example "when a payment succeeds" (lesson 34 covers making them reliable).
- Mobile push notifications (APNs for Apple, FCM for Android): reach phones even when the app is closed (lesson 62).
- WebRTC: a browser technology for peer-to-peer audio and video, where users connect directly (lesson 2 explains UDP, which it often uses).
- MQTT: a lightweight publish/subscribe protocol for IoT (Internet of Things) devices such as sensors.
- WebTransport: a newer option for low-latency, two-way communication. It is built on HTTP/3, which runs on QUIC (a modern transport protocol).
Choosing
- 1Does the server need to push at all?if not, plain REST requests
- 2Short pollingjob status, slow dashboards
- 3Server-Sent Eventsfeeds, notifications, streaming AI text
- 4WebSocketschat, games, collaborative editing
- 5Long pollingthe fallback that works everywhere
Trade-offs
- Polling: simplest, and stateless on the server (the server remembers nothing between requests), but wasteful and delayed.
- SSE: simple, HTTP-friendly and auto-reconnecting, but one-way and text-only.
- WebSockets: the most powerful and efficient for chatty two-way traffic, but stateful. You must handle scaling, routing, reconnection and message recovery yourself.
- All persistent approaches make servers stateful (lesson 7), which complicates deployments, auto-scaling and load balancing.
In the Real World
Slack and Discord use WebSockets to push messages, typing indicators and presence to clients in real time. Discord's "gateway" handles a huge number of WebSocket connections at the same time. Backend systems send events to the right gateway servers.
AI chat assistants. When an AI assistant shows its answer word by word, that streaming is commonly done with Server-Sent Events. Major AI APIs send generated text to developers as SSE events. This shows that SSE fits one-way text that arrives piece by piece.
Food delivery and ride tracking. Seeing your rider move on the map combines techniques: the rider's app sends location updates frequently, and your app receives updates through a persistent connection or frequent polling, with push notifications for major events like "arriving now".
Stock trading apps and live cricket scores push price or score updates many times per second to many users, typically using WebSockets or SSE, with a pub/sub layer fanning the same update out to everyone watching.
Status checks. Many "your export is being prepared" features simply short-poll a job-status endpoint every few seconds. When updates are rare and users are few, it is the simplest reliable choice.
Interview Questions
Q1Compare polling, long polling, SSE and WebSockets.
Short polling asks on a timer. It is simple but wasteful and delayed. Long polling holds each request until there is news. It is near real-time over plain HTTP, but it reconnects all the time. SSE streams events from server to client over one HTTP connection, with reconnect and resume built in. WebSockets upgrade to a two-way channel that stays open. They have the lowest latency, but you handle reconnects and state yourself.
Q2Why is SSE a good fit for streaming AI responses?
The data flows one way, as incremental text. SSE is plain HTTP (works with proxies, auth and HTTP/2), trivial to consume with EventSource, and resumes from the last event ID after a drop. The client sends its prompt with a normal POST.
Q3How do you route a message to a user connected to a different server?
Keep a presence registry of which gateway holds each user's connection, and connect gateways through a pub/sub backplane like Redis, NATS or Kafka. The sender's gateway publishes the message. The recipient's gateway receives it and pushes it down the socket.
Q4How do you avoid losing messages when WebSocket connections drop?
Save messages before pushing them, and treat the push as only a notification. Give messages sequence numbers. On reconnect, the client sends the last number it saw, and the server replays the rest. Use heartbeats to detect dead connections.
Q5What happens to WebSocket clients when you deploy a gateway?
Every connection on it drops. Drain gateways gradually, and make clients reconnect with exponential backoff plus random jitter so thousands of clients do not reconnect at the same instant and overload the remaining servers.
Key Takeaways
- HTTP is client-initiated. For server push, choose between short polling, long polling, SSE and WebSockets.
- Short polling is simple but wasteful. Long polling is near real-time over plain HTTP.
- SSE is one-way, HTTP-friendly and auto-reconnecting, which makes it ideal for feeds, notifications and streaming AI text.
- WebSockets give two-way, low-latency channels, which makes them ideal for chat, games and collaboration.
- Persistent connections need gateways + pub/sub + presence registry, plus heartbeats, reconnect with jitter and message replay. Store first, then push.
Further Reading
- RFC 6455 (The WebSocket Protocol) and RFC 6202 (long polling and HTTP streaming best practices)
- MDN Web Docs guides on the WebSockets API and Server-Sent Events
- High Performance Browser Networking by Ilya Grigorik (chapters on XMLHttpRequest, Server-Sent Events and WebSocket)
- Slack's engineering blog post "Real-time Messaging"
- Discord's blog post "How Discord Scaled Elixir to 5,000,000 Concurrent Users"
Published by Jainik · Last updated · How these lessons are made
Keep learning
Related series