Why you need real-time communication, and when to use it
Normal web traffic works like a question and an answer: the client asks, the server answers, and the connection is done. That is a poor fit when something happens on the server and the client needs to know now: a chat message, a score, a driver's position, a collaborator's edit. The server cannot speak first, so the client has to find a way to hear about changes.
The techniques in this lesson give you two things interviewers always ask about:
- Low latency: how soon a client learns about new data, from "within a few seconds" to "instantly".
- Scalability: how many clients you can keep informed at once, which depends on the cost of each request or each open connection.
Use real-time communication when:
- Users expect to see updates without refreshing: chat, notifications, live scores, dashboards.
- Several users work on the same thing at once: shared documents, multiplayer games, whiteboards.
- Data moves and must be tracked live: delivery tracking, ride-hailing, stock prices.
Skip it, or use something simpler, when:
- Updates are rare and a delay of a minute is fine: ordinary requests or a refresh button are enough.
- The data is the same for everyone and changes slowly: short polling against a cached endpoint is cheap and simple.
- The user is not on the page, such as a closed phone app: use push notifications through the phone's operating system.
Try it
Three clients, A, B and C, want to hear about messages sent at ticks 2, 5, 6, 9 and 13. Choose how they listen. Then inject a failure: the network drops at tick 8 for three ticks. The table shows when each client received each message.
Hearing about new messages in real time
Step 1 of 21 · Press Space to play, arrow keys to stepNo messages yet. Press Play.
Each client asks the server "anything new?" every 4 ticks, at different moments. A message waits until the next ask. Three clients (A, B, C) listen for messages. One tick is one unit of time.
- requests sent:
- 0
- empty answers:
- 0
- average delay:
- -
- worst delay:
- -
- messages lost:
- 0
1 tick = one unit of time. A delay of +3 means the message reached the client 3 ticks after it was sent. Requests count every ask a client makes, including empty ones.
Things to try:
- Short polling: each client asks every 4 ticks. Over the run that is 15 requests, 4 of them empty, and messages arrive an average of about 1.5 ticks late, up to 3 ticks.
- Long polling: every message arrives instantly, and no request is empty. In this short run it makes about as many requests (18), because each answer needs a new request, but each one carries news. Over a long quiet period it would make far fewer than frequent polling.
- WebSocket or Server-Sent Events: just 3 connections and no delay, plus a few small keep-alive messages.
- Then switch on the network failure: short polling delays messages by up to 6 ticks but loses none. Long polling recovers 2 ticks late.
- Push, with "Just listen for new ones": the message sent at tick 9 is lost for all three clients. Switch to "Ask for missed messages": it arrives at tick 11, 2 ticks late, and nothing is lost.
Real-time strategies
| Strategy | How it works | Good when | Weakness |
|---|---|---|---|
| Short polling | The client asks "anything new?" on a timer | Updates are rare or a delay is fine. Easy to cache and to debug | Wasted empty requests. Delay up to the interval |
| Long polling | The client asks and the server holds the request open until there is news | You need near-instant updates but only plain HTTP is available | A new request after every answer. Servers hold many open requests |
| Server-Sent Events (SSE) | One long HTTP response that the server keeps writing events into | One-way updates: feeds, scores, progress bars. Browsers reconnect automatically | Server to client only. Limits on connections per browser over HTTP/1.1 |
| WebSockets | One two-way connection, started from an HTTP request and then upgraded | The client also sends often: chat, games, shared editing | Stateful connections. Needs proxy support and care when scaling |
| Push notifications | The phone's operating system delivers a short message to the device | The app is closed or the screen is off | Not guaranteed or instant. Goes through Apple's or Google's service |
Pull vs push
This is the main fork in the road.
- Pull (short and long polling): the client starts every exchange. It works with plain HTTP, through every proxy and cache, and the server holds no state between requests. The cost is delay and wasted requests.
- Push (SSE, WebSockets): the server can speak first over a connection that stays open. Delivery is immediate and efficient, but every open connection costs the server memory and ties a client to one server, so scaling, deploying and failing over all become harder.
A good rule: start with the simplest option that meets the delay you need. Polling first, then SSE, and WebSockets only when the client also sends often.
How push connections work
A push connection is long-lived and stateful, so most of the design is about what happens around it.
- Connect. A WebSocket starts as a normal HTTP request that asks to upgrade. The server agrees with a
101 Switching Protocolsresponse, and the same connection now carries messages in both directions. SSE simply keeps an HTTP response open. - Authenticate once, when the connection is made, before any data flows.
- Keep alive. The two sides send a tiny message every so often (a heartbeat). It shows the connection is still alive, and it stops proxies and load balancers from closing a connection that looks idle.
- Fan out across servers. A connection lives on one server. If user A is on server 1 and user B is on server 2, a message from A must reach server 2. Servers share messages through a pub/sub layer: each server subscribes to the channels its connected users care about, and the server that receives a message publishes it to the channel. Every subscribed server then pushes it down its own connections.
- Reconnect. Connections drop all the time: networks change, servers restart, proxies time out. The client reconnects with exponential backoff (wait 1 second, then 2, then 4, up to a limit) plus a little random jitter, so thousands of clients do not all retry at the same instant.
- Catch up. A plain connection replays nothing, so messages sent while a client was away are gone unless you stored them. Give each message an increasing ID, store it, and have the client ask for everything after the last ID it saw. SSE has this built in: the browser sends a
Last-Event-IDheader when it reconnects.
Real-time communication vs the alternatives
| Concern | Push connection | Short polling | Long polling | Push notification to a phone |
|---|---|---|---|---|
| Delay | Immediate | Up to the polling interval | Near immediate | Usually quick, not guaranteed |
| Wasted requests | None | Many, mostly empty | Some | None |
| Server cost per client | A held connection | A request now and then | A held request | Almost none |
| Works when the app is closed | No | No | No | Yes |
| Works through any proxy and cache | Needs configuration | Yes | Mostly | Not relevant |
| Easy to scale and deploy | Harder | Easiest | Medium | Easy (a service does it) |
| Best for | Chat, games, live data | Rare or cacheable updates | Real time on plain HTTP | Alerts to a closed app |
Push connections win when delay matters and the user is watching. Polling wins when updates are rare, because it is simpler and costs nothing between requests.
Avoiding a new single point of failure
A real-time setup adds several things that can fail, and one failure can hit everyone at once:
- The connection servers. If a server dies, all its clients drop and reconnect together. Spread connections over several servers with a least-connections balancer, and make clients reconnect with backoff and jitter so they do not overload the rest.
- The pub/sub layer. If it is down, messages stop crossing servers. Run it with replicas or as a cluster, and consider storing messages durably so clients can catch up when it recovers.
- The load balancer. It must support long timeouts and the upgrade headers, and it should be redundant too.
- The message store. The catch-up path depends on it. A message that was only pushed and never stored is lost for anyone who was offline.
The trade-off you accept: holding stateful connections and an extra pub/sub layer, in exchange for instant delivery. Deploy by draining a server gradually (stop accepting new connections, let the old ones move) rather than restarting it in one go.
Common mistakes
- Leaving proxy timeouts at their defaults. NGINX closes an idle upstream connection after 60 seconds unless told otherwise, and many load balancers do something similar. Raise the timeouts and send heartbeats.
- Forgetting the upgrade headers. A WebSocket through a proxy that does not pass
UpgradeandConnectionfails with a plain HTTP error. - Reconnecting immediately. After a restart, every client retries at once and overloads the servers. Use exponential backoff with jitter.
- Assuming nothing is lost. A push connection replays nothing. Store messages with IDs and let clients ask for what they missed.
- Balancing connections by round robin. Connections last hours and vary in activity, so round robin piles them unevenly. Use least connections.
- Sending big messages or no limits. One client sending huge or endless messages can use up a server. Limit message size and rate.
- Using WebSockets where SSE or polling would do. A two-way channel nobody uses is extra complexity to operate.
How to talk about real-time communication in an interview
- Ask what the client needs: how fresh must the data be, does the client send too, how many users at once? Then pick the simplest option that fits and say why.
- Name the transport and its cost: "SSE for one-way updates, WebSockets for chat, short polling if a few seconds of delay is fine".
- Explain fan-out: connections live on one server, so a pub/sub layer carries messages between servers.
- Mention failure unprompted: reconnect with backoff and jitter, heartbeats, and catch-up by message ID so nothing is lost.
- State the trade-off: instant delivery against the cost of holding many stateful connections and the harder deployments that come with them.