Practice this topic in a realistic system design interview
Modern applications often need to show users fresh information without waiting for a page refresh. When a friend sends a message, you expect to see it quickly. When a driver moves, the map should update every few seconds. When a notification is triggered, it should arrive while it is still useful.
These experiences are usually called real-time updates, but "real-time" is not one requirement. It might mean 100 ms for a collaborative editor, 1 second for chat, 5 seconds for driver tracking, or 30 seconds for a dashboard. The target matters because it determines the right transport, cost, and correctness model.
The traditional request/response model is not enough by itself. A client makes a request, the server responds, and the exchange is done. If something changes on the server afterward, the client needs another request or an open channel to learn about it.
This chapter covers the common approaches: short polling, long polling, Server-Sent Events, and WebSockets. The interview skill is not memorizing which one is "best." It is matching latency, directionality, delivery guarantees, scale, and operational complexity to the product requirement.
Real-time updates appear across a wide range of systems:
| Problem | Why Real-time Matters |
|---|---|
| Design Chat System | Messages need low latency, typing indicators need to feel live |
| Design Uber/Lyft | Drivers move continuously, riders need to see position updates every few seconds |
| Design Trading Platform | Price feeds need low latency and clear ordering semantics |
| Design Collaborative Editor | Multiple users editing the same document need low-latency sync |
| Design Notification System | Alerts lose value if they arrive minutes after the triggering event |
| Design Live Sports App | Fans expect scores and play-by-play updates as they happen |
The four common approaches form a spectrum:
Before choosing a mechanism, clarify the requirements:
| Question | Why It Matters |
|---|---|
| What latency is required? | "Real-time" could mean milliseconds, seconds, or "eventually visible" |
| Is communication one-way or bidirectional? | Server-to-client streams fit SSE; frequent two-way interaction points to WebSockets |
| How many clients and messages per second? | Connection count and message fanout often dominate cost |
| Are messages durable? | Pub/sub can wake live clients, but replay needs a log, database, or stream |
| Is ordering required? | Chat rooms, document edits, and trades need sequence IDs or per-topic ordering |
| What happens on reconnect? | Clients need resume tokens, deduplication, and backoff with jitter |
| Can updates be dropped or coalesced? | Presence, typing, and prices can often drop old updates; chat messages usually cannot |
This framing prevents a common interview mistake: picking WebSockets first, then discovering later that the real problem was replay, ordering, fanout, or slow clients.
Each approach sits at a different point on the simplicity-vs-capability spectrum. The sections that follow cover them one at a time.
Short polling is the simplest approach, and often the first thing that comes to mind. The client asks "anything new?" at regular intervals, and the server responds with whatever it has.
The client sets a timer, say every 5 seconds, and sends an HTTP request when it fires. The server checks for new data and responds with updates or an empty response. The client processes any data, waits for the next interval, and repeats.
The approach is predictable and easy to reason about, but it does not scale gracefully when many clients poll frequently.
Short polling forces you into an uncomfortable choice between latency and resource consumption:
| Poll Every 1 Second | Poll Every 30 Seconds | |
|---|---|---|
| Latency | Updates arrive quickly | Updates delayed up to 30 seconds |
| Request Volume | 3,600 requests/hour per client | 120 requests/hour per client |
| Server Load | Server under constant load | Server load manageable |
| Battery | High battery drain on mobile | Battery-friendly |
Poll frequently and you get faster updates but put heavy load on your servers. Poll less often and you save resources but users wait longer for new data. There is no middle ground that escapes the trade-off: lower latency means more requests unless you switch to a different approach.
The numbers add up quickly. With a 5-second polling interval and 1 million active clients, that is roughly 200,000 requests per second, and many of those requests return nothing useful. That is a large infrastructure cost for empty responses.
Despite its limitations, short polling remains a reasonable choice in specific scenarios. It works well for email inbox checks, dashboard refreshes, background sync, and status page monitoring, cases where updates are infrequent and a few seconds of delay is fine.
It is a poor fit for chat, live location tracking, trading platforms, and multiplayer games, where latency is tight or updates are continuous.
If updates are infrequent and a delay of a few seconds is acceptable, short polling keeps your architecture simple. But if the latency target is tight or the client count is large, you need something better.
Long polling changes the basic polling idea. Instead of responding with "nothing new," the server holds the connection open and waits until it has something to send.
This small change makes a significant difference in how the system behaves.
The client sends an HTTP request asking for updates. If the server has new data, it responds. If not, it holds the connection open and waits, typically with a timeout of 30 to 60 seconds.
When new data arrives, the server responds to the waiting request. The client processes the data and opens a new request. This creates a continuous loop where updates arrive quickly without constant empty polling.
The key insight is that instead of the client asking "is there anything new?" over and over, it asks once and waits for the answer. The server only responds when it has something meaningful to say.
The contrast with short polling becomes clear when you look at the request patterns side by side:
In this example, short polling sends 6 requests in 30 seconds, most returning empty. Long polling sends fewer requests, and each one either returns meaningful data or times out.
The challenge with long polling is what happens on the server while it waits. You cannot have a thread sitting in a loop checking for updates. That would waste CPU and limit how many connections you can handle.
Production implementations rely on event-driven architectures and notification systems:
When a request comes in, the handler registers interest in a topic and yields control. It should not busy-wait or block a thread per client. When a new update is published, the relevant waiting handlers wake up and respond.
Pub/Sub or Stream can wake live handlers, but it is ephemeral. If clients need replay after disconnects, use a durable log, stream, or database-backed cursor.
Long polling occupies a useful middle ground. It delivers near real-time updates while staying within the familiar HTTP paradigm.
Long polling works well for notification systems, activity feeds, moderate-scale chat, and progress updates, any application that needs real-time delivery but does not require the client to send frequent messages. Consider alternatives once message frequency is high, the interaction becomes bidirectional, or you need extreme scale. It is a reasonable intermediate step before committing to the complexity of WebSockets.
Server-Sent Events use a different approach. Instead of the client asking for updates repeatedly, it opens a single long-lived connection and the server pushes data through it whenever something changes.
This is closer to how we intuitively think about real-time: the server notifies you when something happens rather than you asking "has anything changed?" over and over.
The client opens a connection using the browser's built-in EventSource API. The server responds with a special content type (text/event-stream) and keeps the connection open. From that point on, the server can push events at any time. The client registers handlers for different event types and processes them as they arrive.
SSE keeps the client-side code small. The browser handles basic connection management, reconnection attempts, and event parsing. Your application still needs to decide how to resume, deduplicate, and handle stale state after a reconnect.
SSE uses a simple text-based protocol. Each message is a series of field-value pairs separated by newlines:
| Field | Purpose |
|---|---|
event | Names the event type so clients can route to specific handlers |
id | Unique identifier for resumption after disconnection |
data | The actual payload, usually JSON |
retry | Tells the client how long to wait before reconnecting |
The text-based format makes SSE easy to debug. You can see exactly what the server is sending by watching network traffic.
A useful part of SSE is browser-managed reconnection. When the connection drops, whether from network issues, server restarts, or anything else, the browser attempts to reconnect.
The browser sends the Last-Event-ID header when reconnecting, allowing the server to resume from a known event ID if the server keeps a replayable event log. This gives you a useful resume mechanism, but it is not durable delivery by itself. The server must store recent events, handle gaps, and deduplicate on the client when needed.
Last-Event-ID supportSSE and long polling look similar, since both hold connections open, but they differ in one important way:
Long polling creates a new HTTP request after each response. SSE streams multiple updates over a single connection. This makes SSE more efficient for frequent server-to-client updates and reduces the gap between responses where messages can be missed unless you use cursors.
SSE fits well when data flows primarily from server to client: stock price tickers, live sports scores, notification streams, build and job progress, and social media feeds. It is a weaker fit for real-time chat, multiplayer games, and collaborative editing, where both sides send messages frequently and WebSockets serve better.
If the client rarely needs to send data, or when a normal HTTP POST is enough, SSE gives you server push with less operational complexity than WebSockets. Many teams default to WebSockets when SSE would serve them better.
WebSockets provide bidirectional communication where both client and server can send messages over a single persistent connection.
While SSE gives you server-to-client push, WebSockets give you a two-way channel. This is useful when both sides send frequent messages, such as chat, collaboration, games, and interactive trading workflows.
The connection begins life as an HTTP request. The client sends an upgrade request asking to switch protocols. If the server agrees, it responds with "101 Switching Protocols" and from that moment on, both sides speak WebSocket instead of HTTP.
Once established, either side can send messages. There is no request-response pattern anymore. The client can send while the server is sending, and messages can flow in both directions over the same connection.
The transition from HTTP to WebSocket happens through a handshake:
After the handshake, messages are exchanged as WebSocket frames. Compared with repeated HTTP requests, WebSockets avoid per-request headers and can be more efficient for high-frequency messaging. The exact frame overhead depends on payload length and client-side masking.
The key difference between WebSockets and the HTTP-based options is the communication model:
| Aspect | HTTP-based Approaches | WebSocket |
|---|---|---|
| Initiation | Client initiates every exchange | Either side can initiate |
| Pattern | Request/Response | No request/response pattern |
| Connection | Request/response or HTTP stream | Long-lived upgraded connection |
| Overhead | Headers on every request | Lower per-message overhead |
With HTTP, every exchange starts with the client. Even with SSE, the client opens the connection and the server responds. With WebSockets, the server can send a message to the client at any time without a new request. This enables patterns like typing indicators, presence updates, and real-time collaboration that would be awkward with request/response HTTP.
WebSockets introduce a scaling complexity that does not exist with stateless HTTP. Each client maintains a persistent connection to a specific server, and those connections are not free. Every open connection holds a socket (a file descriptor) plus send and receive buffers, so memory and the operating system's file-descriptor limit, not CPU, usually set the ceiling.
The default ulimit of 1024 file descriptors per process must be raised before a server can hold meaningful numbers. A reasonable rule of thumb is tens of thousands of idle connections per modern server node.
Tuned setups running event-driven stacks can reach hundreds of thousands to around a million on a single box, with active message traffic lowering that figure.
The practical takeaway for an interview is that you size a WebSocket tier by connection count, not request rate, and add nodes as the connection total grows.
Holding all those connections raises a second problem: what happens when Client A, connected to Server 1, wants to send a message to Client B, who is connected to Server 2?
The common solution is a message bus. When Alice sends a message, Server 1 publishes it to a shared bus, and the bus delivers it to the server holding Bob's connection. There are two ways to wire this up.
The simplest is broadcast: every server subscribes to the same topic, receives every message, and drops the ones for users it does not hold. This is easy to build but wastes work, since each message wakes every node. The alternative is targeted routing: look up which server owns Bob and publish only to that server's channel.
Targeted routing scales better but needs a connection registry, which is the topic of the next section. Either way, Pub/Sub or Stream can work for ephemeral presence or typing events; durable messages usually need Kafka, a database outbox, or another replayable log.
This architecture works, but it adds operational complexity. You need to manage the message bus, handle its failure modes, and design your channel structure thoughtfully.
Targeted routing depends on knowing where each user is connected, so it requires tracking connection state. With HTTP, each request is independent. With WebSockets, you need a registry that maps each user to the server holding their connection, and the message bus uses that map to deliver only to the right node instead of broadcasting to all of them.
You need to handle what happens when a server crashes, when clients reconnect to different servers, and when the connection registry gets out of sync with reality. Use heartbeats, TTLs, and cleanup on disconnect so stale registry entries do not route messages to a server that no longer holds the connection.
WebSockets make sense when you need bidirectional, high-frequency communication. Before choosing them, check whether the client needs to send messages frequently, or whether SSE with occasional HTTP POSTs would work.
Chat applications, multiplayer games, collaborative editing, and interactive trading platforms are strong fits because both sides send messages constantly. For news feeds, notifications, live scores, or progress tracking, the flow is mostly one direction, so SSE is usually simpler and sufficient.
With each approach covered in detail, the table below puts them side by side across the dimensions that matter when choosing one.
| Aspect | Short Polling | Long Polling | SSE | WebSocket |
|---|---|---|---|---|
| Communication | Client-initiated polling | Client-initiated waiting request | Server-to-client stream | Bidirectional |
| Latency | Depends on polling interval | Low when a request is waiting | Low | Low |
| Request overhead | HTTP request each poll | HTTP request after each response or timeout | Single persistent stream | Single upgraded connection |
| Server complexity | Simplest | Moderate | Moderate | Highest |
| Scaling complexity | Easy (stateless) | Medium | Medium | Hard (stateful) |
| Reconnection | Next poll | Manual loop | Browser reconnects; app handles resume | Manual or library-managed |
| Browser support | Universal | Universal | Modern browsers | Modern browsers |
| Firewall friendly | Yes | Yes | Yes | Sometimes blocked |
| Binary data | Via encoding | Via encoding | Via encoding | Native support |
The decision tree for selecting an approach is straightforward once you separate latency, directionality, and scale:
The key questions to ask:
A few rows below look like they break the "seconds of delay is fine" rule. Public tickers and live scores could tolerate a short delay, yet they land on SSE rather than short polling.
The reason is the scale branch in the tree above: these feeds serve a large audience, so the flood of mostly-empty short-polling requests costs more than a single server-to-client stream, even though the latency target is loose.
| Use Case | Recommended Approach | Why |
|---|---|---|
| Email inbox updates | Short polling | Updates every minute is fine, simplicity wins |
| Dashboard metrics | Short polling or SSE | Depends on freshness requirements |
| Notification system | SSE | Server push, built-in reconnection |
| Activity feed | SSE | Continuous stream of updates, one direction |
| Public stock price ticker | SSE | Frequent server-to-client updates |
| Live sports scores | SSE | Real-time server push |
| Build/CI progress | SSE | Progress updates flow one way |
| Basic chat | Long polling or SSE + POST | Works for moderate scale |
| Real-time chat | WebSocket | Typing indicators, read receipts, high frequency |
| Multiplayer games | WebSocket | Low latency bidirectional required |
| Collaborative editing | WebSocket | Continuous bidirectional sync |
| Interactive trading | WebSocket | Bidirectional low-latency workflow |
Regardless of which approach you choose, certain problems appear in every real-time system. Handling them well is the difference between a production-ready system and a fragile prototype.
Connections will drop. Mobile users switch between WiFi and cellular. Servers restart for deployments. Network equipment fails. Your system must handle disconnection gracefully.
The standard approach is exponential backoff with jitter:
Start with a short delay (1 second), double it on each failure, and cap at a reasonable maximum (30 seconds). Add random jitter to prevent thundering herd problems when many clients reconnect simultaneously after a server restart. Reset the delay counter after a successful connection.
A quiet connection and a dead connection look identical from the network perspective. Without active probing, you cannot tell if the other side is still there.
Send periodic pings, every 30 seconds is a reasonable default, and expect a response within a timeout window. If no response arrives, assume the connection is dead and initiate reconnection. This catches scenarios like server crashes, network partitions, and zombie connections that TCP keepalives might miss.
In distributed systems, messages can arrive out of order or be delivered multiple times. This is especially common after reconnections when the client might request a replay of recent messages.
Assign sequence numbers or unique IDs to messages. Track the last processed ID on the client. Buffer out-of-order messages until gaps are filled. Detect and discard duplicates. For SSE, the event ID mechanism helps the client tell the server where to resume, but the server still needs a replayable event source.
Sometimes clients cannot keep up with the rate of incoming messages. A mobile device on a slow connection, a browser tab in the background, or a client doing expensive processing can all fall behind.
Monitor send buffer sizes. When a client falls behind, you have choices: drop non-critical messages, aggregate multiple updates into one, or disconnect the client entirely. The best choice depends on the use case. A stock ticker might drop intermediate price updates and send only the latest.
A chat application might need to deliver every message and should disconnect clients that fall too far behind.
For connections that carry significant data volume, per-message compression can reduce bandwidth substantially. WebSocket supports the permessage-deflate extension, which compresses each message individually. Most WebSocket libraries support this with a configuration flag.
The trade-off is CPU usage for compression and decompression. For text-heavy payloads like JSON, compression can reduce size substantially. For already-compressed data like images, it provides little benefit. Enable it selectively based on payload characteristics and CPU headroom.
Per-request HTTP auth is simple: every request carries a token or cookie and the server checks it each time. Long-lived connections break that model. The client authenticates once at connect time, but the connection can stay open for hours, long enough for the token to expire while the stream is still live.
You cannot re-check a header that is never sent again.
The common pattern is to authenticate during the handshake, then keep the session valid over time. When the token nears expiry, the client sends a refreshed token as an in-band message, or the server closes the connection and the client reconnects with a fresh token. Either way, the connection's authorization is re-established rather than assumed to last forever.
Three security concerns deserve explicit attention:
| Concern | Risk | Mitigation |
|---|---|---|
| Transport encryption | Tokens and payloads sent in clear text | Use WSS for WebSockets and HTTPS for SSE, never plain ws:// or http:// |
| Cross-site hijacking (CSWSH) | A malicious page opens a WebSocket to your server, and the browser attaches the user's cookies automatically | Validate the Origin header on the handshake and prefer an explicit token over cookie-only auth |
| Token leakage | SSE's EventSource cannot set an Authorization header, so tokens often ride in the query string and land in access logs | Use short-lived tokens, or pass auth via a cookie scoped to the streaming endpoint |
The hijacking case is easy to overlook. The browser's same-origin policy does not block the WebSocket handshake, and it sends cookies with it, so cookie-only auth lets any origin open an authenticated socket. Checking the Origin header closes that gap.
Stateless HTTP lets a load balancer send each request to any node. Long-lived connections do not move once established, which changes how the tier in front of them behaves.
First, the load balancer has to understand the connection. A WebSocket starts as an HTTP request with an Upgrade header, so the balancer must either operate at layer 7 and pass the upgrade through, or run at layer 4 as a plain TCP proxy.
Some older proxies strip the upgrade and silently downgrade the connection, which is a common cause of "WebSockets work locally but not in production." Idle timeouts matter too: a balancer that closes quiet connections after 60 seconds will kill an otherwise healthy stream, so heartbeats double as keepalives that reset that timer.
Second, deploys are disruptive in a way they are not for stateless services. Replacing a node drops every connection it holds at once, and those clients reconnect within seconds, all at the same time.
The mitigations are connection draining (stop accepting new connections on a node, then give existing ones a grace period to finish or move before shutdown), staggered rollouts so only a fraction of connections drop at a time, and client-side exponential backoff with jitter so the reconnections spread out instead of arriving in a single burst.
The four approaches are not academic. The systems people use every day pick among them based on exactly the trade-offs covered here: directionality, message frequency, and scale.
| System | Transport | Why |
|---|---|---|
| Slack | WebSocket | Clients hold a persistent connection for real-time events (messages, presence, typing), all of which flow in both directions at high frequency |
| Discord | WebSocket gateway | A single gateway connection carries events, with heartbeats for health and sequence numbers plus a resume protocol to replay missed events after a drop |
| Persistent socket | Mobile clients keep a long-lived connection (historically a customized XMPP over TCP) so messages push instantly; WhatsApp Web uses WebSocket | |
| Figma | WebSocket | Multiplayer editing needs continuous two-way sync of document changes between every connected client and the server |
| Coinbase / Binance | WebSocket feeds | Public market-data APIs stream order-book and trade updates, mostly server-to-client but exposed over WebSocket for low latency and subscription control |
| LLM chat UIs (ChatGPT and similar) | SSE | Token-by-token streaming is one-directional server-to-client, so SSE is a natural fit and avoids the overhead of a full WebSocket |
Two patterns are worth calling out. Discord is a clean example of the reliability machinery from earlier sections in production: a WebSocket carries the live stream, but heartbeats detect dead connections and a sequence-number-plus-resume protocol replays what was missed, because the transport alone does not guarantee delivery.
The LLM streaming case shows the opposite lesson, that the newest real-time feature most engineers have shipped recently, streaming model output, uses SSE rather than WebSocket, precisely because the data only flows one way.
Short polling, long polling, SSE, and WebSockets cover the common cases, but two related technologies come up often enough that you should know where they fit.
None of the four approaches work when a mobile app is backgrounded or closed, because the operating system suspends the app and tears down its connections to save battery. Delivering an alert in that state requires a platform push service: Apple Push Notification service or Firebase Cloud Messaging.
The app server hands the message to APNs or FCM, which maintains its own connection to the device and wakes the app. Real apps combine the two models: a live connection while the app is in the foreground, and push notifications when it is not.
When you need the lowest possible latency or direct peer-to-peer media, such as video calls, voice, or screen sharing, WebRTC is the tool. It establishes a direct connection between browsers (falling back to relays when network conditions require), typically over UDP, which trades guaranteed delivery for speed.
It is more complex to set up than the approaches in this chapter and is usually reserved for real-time audio and video rather than general data updates.
Both of these complement the four core approaches rather than replace them, and a full system often uses several together.
Real-time updates are now expected in many applications. Users expect timely feedback, whether they are chatting with friends, tracking a delivery, or monitoring a build pipeline.
The four approaches we covered form a progression from simple to sophisticated:
The best approach is the one that solves your problem with the least complexity. Start with simpler solutions and evolve only when you have evidence that you need more capability.
20 quizzes