Choosing a real-time technique
How to pick between short polling, long polling, server-sent events, WebSockets, and WebRTC for each feature, starting from who talks, how often, and how fresh the data must be, then checking scale, proxies, battery, and the fallback you will ship.
The problem: five tools, one feature
Your team is adding three features to a project management app in the same quarter. A small badge shows the number of unread notifications, a board view shows cards moving as teammates drag them, and a new AI assistant streams its answer word by word. A product manager asks one simple question: "Do we need WebSockets?"
The honest answer is "for which feature?". The badge changes a few times an hour and nobody minds if it is 30 seconds late. The board needs updates within a second, mostly from server to browser, and the assistant streams one long answer to one request. Three features, three different shapes of traffic, and very possibly three different techniques.
The five tools you already know
The earlier lessons in this module covered each technique on its own. This lesson does not teach them again. It puts them side by side and gives you a way to choose. A quick reminder of what each one is:
- Short polling (see the short polling lesson): the browser asks "anything new?" on a timer, for example every 10 seconds. Plain HTTP, simplest of all, and wasteful when nothing changes.
- Long polling (see the long polling lesson): the browser asks, and the server holds the request open until there is news or a timeout, usually 20 to 30 seconds. Near-instant delivery over plain HTTP.
- Server-sent events, or SSE (see the server-sent events lesson): one
GETresponse that stays open and carries a stream of text events from server to browser.EventSourcereconnects on its own and resumes withLast-Event-ID. - WebSockets (see the WebSockets lesson): one upgraded connection where both sides send messages at any time, with 2 to 14 bytes of framing each. You own the protocol, the reconnects, and the scaling.
- WebRTC (covered in a later lesson): direct browser-to-browser connections for audio, video, and data, built for very low latency media. It is the only option here that can skip your server for the data itself.
Two neighbors also come up in these conversations. Webhooks (see the webhooks lesson) are server-to-server callbacks, so they never reach a browser directly. Push notifications (covered in a later lesson) reach a device even when your page is closed. Both often sit next to the five above in a real design.
How teams choose badly
Most bad choices come from picking the tool first and the requirement second. Three patterns show up again and again:
- WebSockets for everything. A notification badge gets a stateful connection layer, sticky load balancing, a pub/sub bus, and a reconnect protocol, for data that changes twice an hour. Polling every 30 seconds would have cost a few lines of code.
- Polling for everything. A live board polls every second to feel fast. With 20,000 open tabs, that is 20,000 requests per second, almost all of them answered with "nothing new".
- Ignoring the network in between. A WebSocket design works in the office and fails at a customer whose corporate proxy blocks the upgrade, with no fallback planned.
The fix is a short list of questions asked in a fixed order: who sends data, how often, how fresh it must be, how many clients there are, and what the network and device allow. The next chapter turns those questions into one picture.
The mental model: describe the traffic, then pick the pipe
Think of the five techniques as pipes of different sizes and shapes. You would not choose a pipe before you know what flows through it, how much, and in which direction. So the first step is always to describe the traffic of one feature in plain numbers. The choice usually falls out of that description.
Five questions that describe any real-time feature
- Who sends? Only the server, only the client, or both. Count the messages per minute in each direction for one active user.
- How fresh must it be? The longest delay a user would accept before the feature feels broken: 100 ms for a cursor, 1 second for a chat message, 30 seconds for a badge, minutes for a report.
- How many receive the same data? One user (their own order), a small group (a chat channel), or a huge audience (a live score). This is called fan-out.
- Where is the other end? Your server, or another user's device (audio, video, files between people).
- Must it reach a closed page? If the user should hear about it with no tab open, no in-page technique can help.
Write the answers as a small profile. For the three features from the last chapter it looks like this:
| Feature | Who sends | Freshness | Fan-out | Other end | Closed page |
|---|---|---|---|---|---|
| Notification badge | Server, about 3 per hour | 30 s is fine | One user | Server | No (push is a separate feature) |
| Live board | Server, 1 to 20 per minute; clicks go by POST | About 1 s | Team of 5 to 50 | Server | No |
| AI answer stream | Server, 20 to 50 chunks per second, for one request | Each chunk at once | One user | Server | No |
The two axes that matter most
Of the five questions, two separate the techniques most clearly: how much the client sends and how fresh the data must be. Plot a feature on those two axes and you land in the zone of one technique. The other three questions then confirm the choice or move you to a neighbor.
Read the map from the bottom left. Data that can be 30 seconds or more late, with little sent by the client, belongs to short polling. Make it fresher, down to a few seconds or under one, and you move to SSE, with long polling as its fallback.
Add frequent messages from the client and you move to WebSockets. Audio and video between people need delays of tens of milliseconds, and WebRTC is the tool built for that.
"The client sends something" is not enough for a WebSocket
A common slip is to pick WebSockets because the client sends anything at all. Almost every feature has some client actions: posting a comment, moving a card, voting. If those happen a few times a minute, a normal POST is fine, and the updates can still come down an SSE stream.
The question is whether the client sends often, many times a minute, in small pieces where per-request headers would dominate. Typing indicators, cursor positions, and game inputs pass that test. A comment form does not.
The same goes for freshness. "Real-time" in a product spec often means "within a few seconds". Ask what delay would actually hurt. If the answer is 10 seconds, the cheapest technique that meets 10 seconds wins, and that is often polling.
Check your understanding · Exercise 1
A delivery app shows the courier's position on a map while the order is on its way. The position updates every 5 seconds and the customer only watches. Which statements about its traffic profile are right? Select all that apply.
The decision tree
The map gives a feeling for the zones. The decision tree turns it into a fixed order of questions you can walk in an interview, out loud, in under a minute. The order matters: the first questions catch the special cases that no amount of tuning can fix, and the last ones pick between close neighbors.
Step 0: does the server need to speak first at all?
Before the tree, check that you need real-time delivery. A product page's stock count only matters when someone looks at it or adds it to the cart, so a fresh fetch at that moment is enough. A settings page needs nothing. Real-time pays off only when something changes while the user is watching and seeing it late has a cost you can name: a missed chat message, a stale price, two people editing the same card.
Steps 1 and 2: the special cases
- Media or data between users' devices? Voice, video, screen sharing, or large files sent person to person need WebRTC. It is built for media, can send over UDP so a lost packet does not stall the ones behind it, and can connect browsers directly. The WebRTC lesson later in this module covers it. You still need a server channel (often a WebSocket) for signaling, the messages that set the call up.
- Must it reach a closed page? Use web push, covered in the push notifications lesson. It is never the only channel: once the user opens the page, an in-page technique takes over for live updates.
Steps 3 to 5: the main branch
- Does the client send often, many times a minute? Pick WebSockets. One connection carries both directions with a few bytes of framing per message.
- Must updates arrive within a few seconds? Pick SSE. It streams server-to-client over plain HTTP, reconnects on its own, and resumes with
Last-Event-ID. Keep long polling as the fallback for networks that buffer streams. - Otherwise, when a delay of tens of seconds is fine, pick short polling, with an interval that matches the freshness need,
ETagchecks so unchanged answers are a cheap304, and a pause in hidden tabs.
Two adjustments come after the tree. If the same data goes to a huge audience, consider polling a CDN-cached URL even when the tree said SSE, because the cache absorbs the fan-out (the scale chapter shows the math). And if events start on another company's server, they come to your backend by webhook first and then reach the browser by whichever technique the tree picked.
Walking the tree for three features
| Feature | Path through the tree | Pick |
|---|---|---|
| Notification badge | No media, no closed page, client rarely sends, 30 s is fine | Short polling every 30 to 60 s, paused when hidden |
| Live board | No media, no closed page, client sends a few times a minute (POST), needs about 1 s | SSE, with long polling as fallback |
| AI answer stream | No media, no closed page, client sends one question, chunks must arrive at once | A streamed fetch response in SSE format |
| Multiplayer whiteboard | No media, no closed page, client sends cursor moves 10+ times a second | WebSockets |
| Support video call | Media between people | WebRTC, with a WebSocket for signaling |
Note the AI row. EventSource only sends GET requests with no body and no custom headers, so it cannot send a long prompt. AI chat clients usually send a POST with fetch and read the response body as a stream in the same text/event-stream format. It is still server-sent events on the wire, just without the EventSource object and its automatic reconnect.
Check your understanding · Exercise 2
Walk the tree for each feature. Which picks are right? Select all that apply.
Side by side: the comparison table
The tree tells you where to start. The table tells you what you are signing up for. Each column is a technique, and each row is a question an interviewer, or your future on-call self, will ask about it.
| Question | Short polling | Long polling | SSE | WebSockets | WebRTC |
|---|---|---|---|---|---|
| Direction | Client asks, server answers | Client asks, server answers when ready | Server to client; client uses normal requests | Both ways on one connection | Device to device, both ways |
| Typical delay | Up to one interval, plus a request | About one network trip, plus a short gap after each answer | Tens of ms after the event | Tens of ms after the event | Lowest: tens of ms, media-grade |
| Server cost per idle client | Requests: 6 per minute at a 10 s interval | One held request, plus a new one every 20 to 30 s | One open response, plus a heartbeat every 15 to 30 s | One open socket, plus a ping every 20 to 30 s | Signaling only; bandwidth if a TURN relay is used |
| Proxies and firewalls | Works everywhere | Works almost everywhere; idle timeouts need care | Fine over HTTPS; some proxies buffer the stream | Upgrade can be blocked on old or strict proxies; wss:// helps | UDP often blocked; TURN over TCP 443 as last resort |
| Battery on phones | Each poll wakes the radio | Each re-request wakes the radio | Heartbeats wake the radio | Pings wake the radio | Heavy while a call runs, nothing otherwise |
| Reconnect and resume | Nothing to reconnect; send since or ETag | Your loop with a cursor and backoff | Built in: auto reconnect and Last-Event-ID | All yours: backoff, resume message, resubscribe | ICE restart; app must renegotiate |
| Browser support | Everywhere | Everywhere | All modern browsers | All modern browsers | All modern browsers; codecs and features vary |
| HTTP/1.1 connection use | Brief, per request | Holds one of 6 per host (fine on HTTP/2) | Holds one of 6 per host (fine on HTTP/2) | Its own connection, not counted in the 6 | Its own transport |
Reading the rows that people get wrong
Delay is not only the network trip. With short polling, an event that happens just after a poll waits the whole interval, so the average delay is half the interval and the worst is a full one. With long polling, the server answers at once, but there is a gap while the next request travels.
SSE and WebSockets push the moment the server knows, so their delay is close to one network trip, often 20 to 100 ms. The race below delivers the same four server events with each technique; change the polling interval and watch requests trade against waiting.
| Technique | Requests in 20 s | Average wait per event |
|---|---|---|
| Short polling | 5 | 2.3 s |
| Long polling | 5 | almost none |
| Server-sent events | 1 | almost none |
| WebSocket | 1 | almost none |
Server cost is a different kind of cost per column. Polling costs requests per second: CPU, logs, auth checks, and database reads, even when the answer is empty. Persistent techniques cost open connections: memory per connection (a few KB to tens of KB on an event-loop server), file descriptors, and load balancer slots. Neither is free, and which one is cheaper depends on how often data changes compared with how often you would ask.
Reconnect and resume is where the work hides. EventSource reconnects by itself and sends the last event id it saw. A raw WebSocket does neither: when it closes, it stays closed until your code opens a new one and tells the server where it left off. That code is easy to get subtly wrong, and it is the code that runs during your worst outages.
What the table leaves out
- Binary data. SSE is text only (UTF-8). If you need binary, encode it (base64 adds about 33 percent) or use WebSockets, which carry binary frames.
- Message ordering. Within one response or one connection, every technique here delivers in order, except WebRTC data channels, which can be set to unordered for lower delay. Across reconnects, ordering is yours to restore with sequence numbers.
- Caching. Only short polling plays well with HTTP caches and CDNs. A held request or an open stream is never cached.
- Authentication. Polling and long polling send cookies and headers like any request.
EventSourcesends cookies (cross-origin only withwithCredentials: true) but no custom headers, so it relies on the session cookie or a short-lived ticket in the URL.WebSocketsends cookies on the handshake but noAuthorizationheader, so teams use the cookie with anOrigincheck, a short-lived ticket in the URL, or a token in the first message (all covered in the WebSockets lesson).
Check your understanding · Exercise 3
Your app polls every 20 seconds. A teammate says "so updates arrive within 20 seconds, 10 on average". What else adds delay, or changes the numbers? Select all that apply.
Scale: requests, connections, and fan-out
At small scale every technique works, and the choice is about simplicity. At large scale the cost model decides. Polling turns users into requests per second, and persistent techniques turn users into open connections. A design review usually starts with one quick calculation of each.
The two numbers to compute
- Polling load = online clients divided by the interval. 100,000 tabs polling every 10 seconds is 10,000 requests per second. If data changes once a minute, 5 out of 6 of those answers say "nothing new".
- Connection count = online clients, all the time. 100,000 tabs means 100,000 open connections. An event-loop server (Node.js, Go, Java with Netty, async Python) can typically hold tens of thousands each (an assumption to measure on your own stack), so that is a handful of machines, plus the pub/sub layer that tells each machine which events its clients care about.
Connections are often cheaper than they sound, and requests are often more expensive. Each poll pays for TLS work, auth, routing, logging, and usually a database or cache read. An idle open connection costs memory and a heartbeat every 20 to 30 seconds. Try the numbers yourself:
| Approach | Load | Average delay |
|---|---|---|
| Short polling | 6,667 requests per second | about 7.5 s |
| SSE or WebSocket | 100,000 open connections, about 4 servers at an assumed 25,000 each | under 0.2 s |
Many polls come back empty. Compare the request bill with running the connection fleet below.
Fan-out changes everything
Fan-out is how many clients receive the same message. A direct message goes to 2 people, a channel message to 50, and a goal in a cup final to 2 million people at the same second. The same technique can be cheap at a fan-out of 50 and painful at 2 million, because every message must be written to every open connection.
For huge fan-out of identical data, polling a CDN can beat any push technique. Put the score at GET /match/481/score with Cache-Control: public, max-age=2. Two million clients polling every 5 seconds send 400,000 requests per second, but they all land on the CDN.
Turn on request collapsing (the CDN sends one origin request for many clients waiting on the same expired URL), and each CDN location asks your origin about once every 2 seconds. Your servers then see a few hundred requests per second. The cost is a delay of up to about 7 seconds (2 seconds of cache age plus the 5 second interval), which a score page can accept and a chat cannot.
Decision A live score is watched by 2 million people at once. How do you deliver it?
What we pick: For a score page where a few seconds is fine, CDN polling is the cheapest and sturdiest. For live betting, where seconds cost money, pay for push, and buy it from a provider unless real-time is your core product.
Reconnect storms
Persistent connections have a cost that polling does not: they all fail together. A deploy, a load balancer restart, or a short network cut closes 50,000 connections at once, and 50,000 clients try to reconnect in the same second. Each reconnect pays for a TLS handshake, an auth check, and a resubscribe, so the fleet can fall over just as it comes back.
The defenses are the same for every technique: exponential backoff with random jitter so reconnects spread over 10 to 30 seconds, a server that can ask clients to wait (retry: in SSE, Retry-After on HTTP), gradual deploys that drain connections a slice at a time, and resume by cursor so each client asks only for what it missed. The retries and backoff lesson covers the formulas.
Check your understanding · Exercise 4
An internal dashboard has 2,000 users. Data changes every few seconds and should show within about 2 seconds. Which arguments are sound? Select all that apply.
The network in between: proxies, firewalls, and timeouts
Your connection does not go straight from the browser to your server. It may pass a home router, a carrier's network, a corporate proxy, a CDN, and your own load balancer. Each of them was built mostly for short request and answer traffic, and each can quietly break a long-lived one. This is the part of the choice that a demo on office Wi-Fi never shows.
Three ways the middle breaks real-time traffic
- Dropped upgrades. A WebSocket starts with an HTTP request carrying
Upgrade: websocket. An old or strict proxy that does not understand it may strip the header or reject the request, and the socket never opens. - Buffering. Some proxies, and some server setups such as nginx with its default
proxy_buffering on, collect a response before passing it on. A stream of small SSE events then arrives in clumps, or not until the buffer fills. SetX-Accel-Buffering: nofor nginx, and test through your real proxy chain. - Idle timeouts. Load balancers and proxies close connections that send nothing for a while; 60 seconds is a common default, for example on AWS Application Load Balancers and in nginx's
proxy_read_timeout. A quiet stream or a long-poll held too long is cut without warning.
Each break has a matching fix. Heartbeats every 15 to 30 seconds keep idle timeouts from firing: an SSE comment line, a WebSocket ping, or a long-poll timeout answered before 30 seconds. Disabling buffering fixes clumps. And for dropped upgrades, the main fix is encryption.
Why TLS fixes most of it
A proxy can only change what it can read. Over https:// and wss://, the traffic is encrypted end to end, so a proxy in the middle sees an opaque stream on port 443 and passes it along. That is why wss:// gets through far more networks than ws://. Always use wss://; plain ws:// is both unsafe and less likely to get through.
There is one exception. Many companies run TLS-inspecting proxies that decrypt traffic with their own certificate, which the company installs on its laptops. Those proxies can read and change everything, so they can still block upgrades, buffer streams, or cut connections after a fixed time. If your product sells to large companies, assume some customers sit behind one.
WebRTC and blocked UDP
WebRTC prefers UDP, because media can skip a lost packet instead of waiting for it. Many corporate and hotel networks block outgoing UDP, and some routers block direct paths between devices. When no direct path works, WebRTC uses a TURN relay: a server you run (or rent) that forwards the media.
The last resort is TURN over TCP or TLS on port 443, which looks like ordinary HTTPS to a firewall. It works almost anywhere, at the cost of more delay and your bandwidth bill. The WebRTC lesson covers STUN and TURN in detail.
Check your understanding · Exercise 5
Users at one enterprise customer say live updates arrive in bursts every 30 seconds or so, while everyone else sees them at once. Your app uses SSE over HTTPS. What are likely causes? Select all that apply.
Phones, batteries, and background tabs
On a laptop plugged into the wall, a request every few seconds costs nothing anyone notices. On a phone it can cost a visible share of the battery. The reason is not the bytes. It is the radio.
The radio tail
A phone's cellular radio has a low-power idle state and a high-power active state. Sending or receiving anything moves it to the active state, and after the transfer it stays active for several seconds in case more data comes, before it drops back to idle. That waiting period is called the tail. Its length is set by the mobile network and varies by carrier and generation (one widely cited textbook example uses 10 seconds), so a 300-byte poll can cost seconds of full-power radio time.
So the rule on mobile is fewer, larger wake-ups. Ten small messages spread over a minute cost far more than the same ten sent together. This applies to every technique: a poll every 15 seconds, a WebSocket ping every 15 seconds, and an SSE heartbeat every 15 seconds all wake the radio just as often. A persistent connection is not automatically kinder to the battery than polling; what matters is how often anything moves.
Moves that save battery
- Batch on the server. Send presence and counter updates at most once every few seconds, merged, instead of one message per change.
- Stretch heartbeats. Use the longest heartbeat your proxies allow. If the load balancer cuts at 60 seconds, 25 to 30 seconds is enough; 5 seconds is waste.
- Back off when idle. If the user has not touched the page for a minute, poll less often or slow the stream's updates.
- Stop in hidden tabs. Close the stream or pause polling when the page is hidden, and catch up with one request when it is visible again.
- Use push for closed pages. The device or browser already keeps one shared connection to its push service for all apps and sites, so web push adds no connection of yours.
Background tabs: the browser decides for you
Browsers already limit work in tabs the user cannot see. Timers in hidden tabs are slowed, in some cases to once a minute, and on phones a background tab may be frozen or discarded entirely. A polling loop built on setTimeout slows down without telling you. A WebSocket or SSE stream may stay open, be frozen, or be closed by the system, and none of this is consistent across browsers, so do not rely on it.
Take control instead. Listen for visibilitychange, pause on hidden, and on visible do one catch-up request from your cursor before resuming the live channel. This works the same for every technique, which is a good reason to put it in your shared real-time client rather than in each feature:
let hiddenTimer: ReturnType<typeof setTimeout> | undefined;
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
hiddenTimer = setTimeout(() => realtime.pause(), 30_000);
} else {
clearTimeout(hiddenTimer);
realtime.resume({ after: store.lastEventId }); // catch up, then go live
}
});
Check your understanding · Exercise 6
A fitness app's web page keeps a WebSocket open with a ping every 10 seconds and sends step counts as they change. Users complain about battery. Which changes help most? Select all that apply.
Fallbacks: a ladder of transports
Because the failures live in the network, a production real-time layer rarely bets on one technique. It keeps a ladder: the best transport first, then simpler ones that get through more networks. The usual order is WebSocket, then SSE, then long polling, and short polling as the very last step. A one-way feature starts the ladder at SSE.
How to tell that a rung failed
You cannot ask the browser "will this work here?". You find out by trying, with deadlines:
- Open timeout. If a WebSocket has not fired
openwithin about 5 to 10 seconds, treat it as blocked. - First-message timeout. Have the server send a hello event right after connecting. If an SSE stream opens but no hello arrives within a few seconds, something is buffering it.
- Repeated early drops. If the connection opens but dies after the same short time, again and again, a proxy is cutting it. Two or three such drops in a row means step down.
When a rung fails, step down and remember it for the session, for example in sessionStorage, so every page load does not pay the same 10-second timeout again. Try the better rung again later, perhaps on the next session, because the user may have moved from the office network to home Wi-Fi.
One message format on every rung
A fallback is only safe if switching rungs never loses or repeats a message. That needs the same rules on every transport: each event has an increasing id, the client keeps the last id it handled, and every rung can resume with "give me everything after id N". With that, a step down is just a reconnect on a different pipe. Hide all of it behind one small interface, so components never know which rung is in use:
type RealtimeEvent = { id: number; topic: string; data: unknown };
interface Transport {
connect(after: number): Promise<void>; // resume after this event id
onEvent(handler: (e: RealtimeEvent) => void): void;
send?(topic: string, data: unknown): void; // WebSocket only; others use fetch
close(): void;
}
// Components only ever see this:
realtime.subscribe("board:42", (e) => applyBoardEvent(e.data));
realtime. Features subscribe to topics and never see a transport.Build, use a library, or buy
Decision Who builds the ladder, reconnects, and fan-out?
What we pick: For most product teams, a library or a hosted service is the right first step. Build your own only when real-time is the product itself, or the bill at your scale clearly pays for the team.
Check your understanding · Exercise 7
Your chat app falls back from WebSocket to long polling for some users. Which designs keep messages from being lost or shown twice when it switches? Select all that apply.
In real products
Real products mix techniques, one per feature, each the cheapest that meets that feature's needs. The table shows typical choices for well-known kinds of product. Companies change their stacks over time and rarely document every detail, so read these as common, publicly described patterns rather than exact current internals.
| Kind of product | Feature | Typical technique | Why it fits |
|---|---|---|---|
| Team chat (Slack-style) | Messages, typing, presence | WebSockets | Many small messages in both directions, all day long |
| Design tools (Figma-style) | Multiplayer editing and cursors | WebSockets | Every edit and cursor move goes both ways within milliseconds |
| AI chat assistants | The answer as it is written | Streamed fetch in SSE format | One POST per question, then text flows one way for many seconds |
| Gaming chat (Discord-style) | Events and voice | WebSocket for events, WebRTC for voice | Events go through servers; voice needs the lowest delay |
| Video meetings (Meet-style) | Calls and screen share | WebRTC, with a server channel for signaling | Real-time media between people |
| Public APIs (GitHub-style) | Activity and notifications | Polling with ETag and a server-suggested interval | Any client can do it, and 304 answers are cheap |
| Payments (Stripe-style) | Payment events to merchants | Webhooks, then the merchant's own choice to the browser | Server to server, with retries and signatures |
| Live sports pages | Scores | Polling a CDN-cached URL, or a push provider | Huge fan-out of identical data |
One app, many pipes
Go back to the project management app from the first chapter and put all its features together. A realistic design ends up with four techniques, each behind the same shared client, and only one stateful connection fleet:
- Badge and inbox: short polling every 60 seconds, paused when hidden, plus web push for mentions when the page is closed.
- Board: an SSE stream per open board, with moves sent by
POST. If the team later adds live cursors, this one feature moves to a WebSocket. - AI assistant: a streamed
fetchresponse per question, closed when the answer ends. - Integrations: webhooks from GitHub or a calendar arrive at the backend, which publishes them to the board's topic, so they reach the browser through the same SSE stream.
Lab: set the requirements, get the pick
Set the requirements of a feature you know, or one from an interview prompt. The lab walks the same tree as this lesson, names a primary technique and a fallback, and shows which answer decided it. Try changing one control at a time to find where the pick flips.
- Updates flow from server to client and must arrive within seconds, so a one-way stream fits.
- The client's few actions go as normal POST requests.
- Fallback: long polling, speaking the same message format with event ids.
Check your understanding · Exercise 8
A stock trading app shows live prices on the watchlist and lets users place orders. Which split is sensible? Select all that apply.
Using it in a system design interview
Almost every frontend system design prompt has a real-time part: a chat, a feed, notifications, a dashboard, a collaborative editor. Interviewers rarely care which technique you name. They care whether you can justify it from requirements, say what it costs, and say what happens when it fails.
When to bring it up
Ask the traffic questions during requirements, before you draw anything: "How fresh must this be? Does the user send often, or mostly watch? How many people see the same update? Should they hear about it with the tab closed?". The answers set up the choice, and asking them shows you know the choice depends on them.
Then make the choice in the API or interface part of the design, one feature at a time. Say it as a sentence with a reason and a cost: "Board updates go over SSE: they flow one way, need about a second, and SSE reconnects and resumes by itself. Moves are plain POSTs. If the corporate proxy buffers the stream, we fall back to long polling."
What to draw
- The browser's real-time client as one box that all features use, with the transport inside it.
- The connection tier (gateway servers holding the connections) separate from the API servers, with a pub/sub box between them and the services that produce events.
- An arrow for resume: the client sends its last event id on reconnect, and the server answers from a short event log.
How deep to go by level
| Level | What a strong answer covers |
|---|---|
| Junior | Picks a technique that fits the direction and freshness, and explains why polling or a socket would be worse here. |
| Mid-level | Adds reconnects with backoff and jitter, resume by event id, pausing in hidden tabs, and the HTTP/1.1 limit of 6 connections per host for streams. |
| Senior | Adds the cost model (requests versus connections, fan-out, CDN polling for huge audiences), proxies and idle timeouts, a fallback ladder with one message format, battery, and a build-or-buy call with reasons. |
Summary
- Choose per feature, not per app. Describe the traffic first: who sends, how fresh, how many receive it, where the other end is, and whether the page is open.
- The decision tree: media between people means WebRTC; a closed page means web push; frequent client messages mean WebSockets; server updates needed within seconds mean SSE; otherwise poll.
- Polling costs requests per second; persistent techniques cost open connections. Compute both before choosing.
- For huge fan-out of identical data, polling a CDN-cached URL can beat any push technique, at the cost of a few seconds.
- Proxies and firewalls break long-lived connections: use TLS everywhere, heartbeats under idle timeouts, and a fallback ladder that detects failure by timeouts.
- On phones, every wake-up of the radio costs battery. Batch, stretch heartbeats, and pause in hidden tabs.
- Every rung of the ladder must speak one message format with increasing ids and resume by cursor, so switching transports never loses or repeats a message.
In the interview
Questions interviewers ask about this. Answer each out loud first, then compare.
Question 1
We're adding live updates to a dashboard used by about 5,000 people. How would you choose the technique?
I'd start with the traffic profile. Who sends: probably only the server, with filters changed by normal requests. How fresh: I'd ask; if a few seconds is fine, polling may be enough. Fan-out: each dashboard is likely per team, so a CDN cannot share answers.
If it needs about a second, I'd use SSE: one-way, plain HTTP, built-in reconnect with Last-Event-ID, and 5,000 connections fit on one or two event-loop servers. I'd add a heartbeat every 25 seconds for proxy idle timeouts, pause in hidden tabs, and fall back to long polling if the stream is buffered.
If 10 to 30 seconds is fine, polling with ETag is simpler: 5,000 users at 15 seconds is about 330 requests per second, mostly cheap 304s.
Question 2
Why not just use WebSockets for everything?
Because a WebSocket is the most work to run well. You own the message protocol, reconnects, resume, auth on the handshake, and a stateful fleet with sticky connections and pub/sub. You lose HTTP caching and the 'any server can answer' model.
For features where only the server talks, SSE gives the same freshness with less code. For slow-changing data, polling is cheaper still. I'd use a WebSocket where the client sends often, like cursors, typing, or game input, and keep everything else on simpler pipes.
Question 3
A live event page will have 3 million viewers for 2 hours. What would you do?
First, the freshness need. If a few seconds is fine, I'd serve the state at a cacheable URL with max-age of 1 to 2 seconds and have clients poll it every few seconds. The CDN absorbs hundreds of thousands of requests per second, and with request collapsing my origin sees a few hundred.
If it must be sub-second, I'd use a push provider or CDN push product built for this fan-out rather than my own fleet, and plan for the reconnect storm at the start, with jittered backoff and a snapshot URL for the initial state.
Question 4
Some enterprise customers say real-time updates don't work for them. How do you investigate and fix it?
It's almost always the network in between. I'd check client logs for upgrades that never open, streams that open but deliver nothing, or connections cut at a fixed time. Those point to a proxy blocking Upgrade, buffering the response, or an idle timeout.
Fixes: make sure we only use wss:// and https://, add heartbeats under the idle timeout, disable buffering where we control the proxy, and add a fallback ladder that detects failure with timeouts and steps down to long polling, remembering the choice for the session.
Question 5
How would you design the real-time parts of a video calling app?
WebRTC for audio, video, and screen share, with STUN for discovering addresses and TURN as a relay, including TURN over TCP 443 for networks that block UDP. Beyond a few people, a media server (an SFU) forwards streams instead of a full mesh.
A WebSocket for signaling, in-call chat, hand raises, and the participant list, since those go both ways and need to be fast. Web push to ring a user whose tab is closed. Plain HTTP for scheduling and history.
Practice it
These design problems lean on choosing a real-time technique.
- Design an embeddable poll widgetMedium
- Design an online chess gameMedium
- Design an AI image generation studioMedium
- Design a news feedHard
- Design live comments for a sports streamHard
- Design a chat appHard
- Design a multiplayer trivia gameHard
- Design Uber's rider web appHard
- Design an IoT dashboard for thousands of sensorsHard
- Design a collaborative pixel canvas like r/placeHard
- Design a Twitch-style live stream pageHard
References
- MDN: Using server-sent events
- MDN: The WebSocket API
- MDN: WebRTC API
- MDN: Push API
- RFC 6202: Known issues and best practices for long polling and streaming Still the best summary of how proxies and timeouts affect HTTP-based push.
- Socket.IO: How it works A real fallback ladder: long polling first, then upgrade to WebSocket.
- High Performance Browser Networking: Mobile Networks Radio states and the energy tail that makes frequent small transfers costly on phones.
- Chrome for Developers: Page Lifecycle API How browsers freeze and discard background tabs.
Read next
- Short pollingThe browser asks the server "anything new?" on a timer: the simplest way to keep a page fresh, and the right answer more often than people think.
- Long pollingThe browser asks for updates and the server holds the request open until it has something to say: near-instant updates using nothing but ordinary HTTP, at the price of one parked request per user.
- Server-sent eventsOne HTTP response that never finishes: the server keeps writing small text events to it, and the browser's
EventSourceparses them, reconnects on its own, and resumes where it left off. The simplest push channel when data flows from server to client. - WebSocketsOne long-lived connection, opened by an HTTP handshake, on which browser and server can each send a message at any moment. The usual pick for chat, multiplayer, and collaboration, and the one where you own the protocol, reconnects, and scaling.
- WebRTCThe browser's built-in way to send audio, video, and data directly between users' devices with very low delay: the base of every web video call, and a design with more servers than its "peer to peer" name suggests.
- Web push notificationsMessages your server sends through the browser's push service, which wake your service worker to show a notification even when your site isn't open: powerful for re-engagement, and easy to ruin with a bad permission prompt.