You open Slack and a message appears instantly. You watch a GitHub Actions pipeline log stream line by line. You see your Uber driver's marker glide across the map in near real-time. None of that happens by accident. Behind every live update is a deliberate choice about how the client and server talk to each other.
Most of the web runs on a simple request-response model: your browser asks for something, the server responds, and the connection closes. That works great for loading a webpage or submitting a form. But what happens when the server needs to push something to you without you asking? That's where things get interesting, and where Polling, Server-Sent Events, and WebSockets each play a different role.
Let's walk through all three, starting with the simplest, so you can make an informed choice the next time you're building something live.
The Problem: HTTP Only Answers When Asked
By default, HTTP is a one-way street. The client drives. The server just waits to be asked.
That's fine until you need to build a chat app where new messages should appear immediately, or a dashboard that shows live server metrics, or a notification badge that lights up when something happens. In those cases, the server knows something happened before the client does, but the client never asked.
Developers have solved this in three main ways, each with a different tradeoff between simplicity and capability.
Polling: The Simplest Approach
How It Works
Polling is exactly what it sounds like. The client repeatedly asks the server: "Anything new?" The server answers yes or no, the connection closes, and the client asks again after a short wait.
Short-polling fires requests on a fixed interval (every 5 seconds, every 30 seconds, whatever you configure). Long-polling is a smarter variation: the client sends a request, and if there's nothing new, the server holds the connection open until something happens (or a timeout fires), then responds and the client immediately sends the next request.
The Analogy
Imagine you're waiting for a package. You don't have tracking. So every hour you walk to the front door, look outside, and walk back. The package might arrive at 2:03 PM but you won't know until 3:00 PM when you check again. That lag is inherent to polling.
Sequence Diagram
sequenceDiagram
participant Client
participant Server
Client->>Server: GET /updates
Server-->>Client: 200 OK (no new data)
Client->>Server: GET /updates
Server-->>Client: 200 OK (no new data)
Client->>Server: GET /updates
Server-->>Client: 200 OK (new message: "Hello!")
Code Example
// Ask the server for updates every 5 seconds
setInterval(async () => {
const res = await fetch("/api/updates");
const data = await res.json();
if (data.message) {
console.log("New message:", data.message);
}
}, 5000);
When Polling Makes Sense
- You need something quick to ship and real-time latency isn't critical
- Updates happen infrequently (checking if a background job finished)
- Your infrastructure doesn't support persistent connections
Where It Falls Apart
Every request has overhead: a full HTTP handshake, headers, parsing. If nothing is new, you've burned resources for nothing. With 1,000 users polling every 5 seconds, that's 200 requests per second hitting your server, most of them pointless. It's wasteful at scale and still introduces noticeable lag.
Server-Sent Events: One-Way Streaming From the Server
How It Works
SSE flips the polling model. Instead of the client repeatedly asking, it opens a single connection and the server keeps it open, streaming updates down the wire whenever something happens. The client just listens.
The browser has a built-in API for this called EventSource. Under the hood, SSE uses a plain HTTP connection with a special content type (text/event-stream), and the server sends newline-delimited messages over that open connection. Browsers handle reconnection automatically if the connection drops.
The Analogy
Think of a stock ticker on a financial news channel. You tune in once and prices stream to you continuously. You don't ask for each update. The broadcast just keeps coming. SSE works the same way: one connection, server pushes, client receives.
Real products that use SSE: ChatGPT streams its reply token by token using SSE. GitHub Actions streams build logs in real-time. Many notification systems use it for one-way alerts.
Sequence Diagram
sequenceDiagram
participant Client
participant Server
Client->>Server: GET /stream (Accept: text/event-stream)
Server-->>Client: data: {"score": "1-0"}\n\n
Server-->>Client: data: {"score": "2-0"}\n\n
Server-->>Client: data: {"score": "2-1"}\n\n
Note over Server,Client: Connection stays open
Code Example
// Open a persistent connection; browser reconnects automatically on drop
const source = new EventSource("/api/stream");
source.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("Update:", data);
};
source.onerror = () => {
console.error("Connection lost, browser will retry...");
};
When SSE Makes Sense
- You only need the server to push to the client (not the other way)
- Live feeds: dashboards, logs, AI-generated text, sports scores, notifications
- You want simplicity: SSE works over regular HTTP/2, plays well with proxies, and reconnects on its own
What SSE Cannot Do
SSE is one-directional. The client can receive, but it cannot send messages back over the same connection. If you need back-and-forth communication (like a chat where both parties type), SSE alone isn't enough. You'd pair it with regular POST requests or reach for WebSockets instead.
WebSockets: Full Two-Way Communication
How It Works
WebSockets establish a persistent, bidirectional channel between client and server. It starts as an HTTP request, but the client sends an Upgrade: websocket header asking to switch protocols. The server agrees, the handshake completes, and from that point on both sides can send messages to each other freely, with no request-response cycle needed.
The connection stays open until one side closes it. Messages travel in both directions at any time, with very low overhead per message since you skip the HTTP header cost after the initial handshake.
The Analogy
Think of the difference between sending letters and making a phone call. Polling is sending letters: you mail a letter, wait for a reply, mail another. SSE is like a radio broadcast where one party talks and many listen. WebSockets are a phone call: once connected, both parties can speak whenever they want, and the line stays open until someone hangs up.
Slack, Discord, Google Docs, multiplayer games: anything with real, bidirectional live interaction uses WebSockets (or a protocol built on top of them).
Sequence Diagram
sequenceDiagram
participant Client
participant Server
Client->>Server: HTTP GET /chat (Upgrade: websocket)
Server-->>Client: 101 Switching Protocols
Note over Client,Server: WebSocket connection established
Client->>Server: "Hey, I joined!"
Server-->>Client: "Welcome!"
Server-->>Client: "Alice says: Hello"
Client->>Server: "Hello everyone"
Server-->>Client: "Bob says: Hi!"
Code Example
const socket = new WebSocket("wss://example.com/chat");
socket.onopen = () => {
socket.send(JSON.stringify({ type: "join", name: "Saurabh" }));
};
socket.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log("Received:", msg);
};
socket.onclose = () => {
console.log("Connection closed");
};
When WebSockets Make Sense
- Chat applications, collaborative editing, multiplayer games
- Anything where the client also needs to send data in real-time
- When you need sub-100ms latency for user-to-user interactions
The Added Complexity
WebSockets are stateful. Your server needs to track which connections are open, handle disconnects gracefully, and scale carefully, because a standard load balancer will break WebSocket sessions unless it's configured for sticky sessions or you use a pub/sub layer (like Redis) behind the scenes. SSE doesn't have this problem because it's just HTTP.
Side-by-Side Comparison
| Feature | Polling | SSE | WebSockets |
|---|---|---|---|
| Connection type | New request each time | Single persistent HTTP | Single persistent WS |
| Direction | Client → Server (repeated) | Server → Client only | Bidirectional |
| Browser support | Universal | All modern browsers | All modern browsers |
| Reconnection | Manual | Automatic | Manual |
| Complexity | Low | Low–Medium | Medium–High |
| Overhead per message | High | Low | Very low |
| Works through proxies | Yes | Yes | Needs configuration |
| Best for | Infrequent updates, simple setup | Live feeds, streaming output | Chat, games, collaboration |
How to Choose the Right One
Ask yourself these questions before deciding:
Does the server need to push data to the client without the client asking?
- No → stick with regular HTTP requests
- Yes → keep reading
Does the client also need to send data back in real-time (not just receive)?
- No → SSE is probably enough
- Yes → WebSockets
How often do updates happen?
- Every few minutes or on-demand → Polling is fine
- Continuously or on unpredictable events → SSE or WebSockets
Quick decision guide:
- Building a chat app or multiplayer feature? → WebSockets
- Building a live feed, notification system, or streaming AI output? → SSE
- Building a background job status checker or infrequent sync? → Polling
Most teams reach for polling first because it's fast to ship. When the limitations start hurting (too much server load, too much lag), they graduate to SSE. WebSockets come in when the product genuinely needs two-way real-time interaction.
Closing Thoughts
There's no universally right answer. Polling is not bad engineering. It's often the correct choice for low-frequency updates. SSE is underused and solves a surprisingly wide range of problems elegantly. WebSockets are powerful but come with real operational complexity that you should only take on when you actually need bidirectional communication.
The real skill isn't memorizing which protocol does what. It's knowing the tradeoffs well enough to make the call confidently when your team asks: "How should we build this?"
Now you do.