When building applications that require live updates—such as notification feeds, order tracking pages, live dashboard metrics, or progress indicators—developers often default to HTTP polling. Setting up an interval timer to send fetch requests every few seconds feels quick and effortless during initial prototyping. However, as your user base grows, naive polling quickly manifests as a silent resource killer, flooding your backend with redundant connections and unnecessary database queries.
In this article, we will analyze why short polling breaks down at scale, why WebSockets might be overkill for unidirectional updates, and how Server-Sent Events (SSE) offer a clean, robust, and lightweight solution.
The Cost of Naive Polling
HTTP short polling operates on a simple pattern: the client asks the server "Is there new data?" at fixed intervals, say every 3 seconds.
// A common polling implementation
setInterval(async () => {
const response = await fetch('/api/orders/123/status');
const data = await response.json();
updateUI(data);
}, 3000);
While conceptually simple, this pattern introduces severe structural inefficiencies:
- Massive Overhead: Every HTTP request requires TCP handshake overhead, TLS negotiation, and header payload transfer. If 1,000 active users poll every 3 seconds, your server handles over 330 HTTP requests per second—even if no underlying data has changed.
- Wasted Database Work: Each request usually triggers a database lookup or cache query to verify status, consuming CPU cycles and connection pool slots needlessly.
- Battery and Network Drain: Mobile devices are kept in a high-power state to process continuous network requests, degrading battery life and consuming mobile data.
Some teams attempt to mitigate this using HTTP long polling, where the server holds the request open until new data arrives. While long polling reduces request volume, it still incurs connection teardown and setup overhead for every single event update, leading to complex connection management on both ends.
Is WebSockets the Only Answer?
When polling shows its limits, engineers frequently jump straight to WebSockets. WebSockets establish a full-duplex, bi-directional TCP connection between the client and server. It allows both parties to send messages freely at any time.
WebSockets are indispensable for interactive applications requiring two-way real-time communication, such as collaborative document editing, multi-player gaming, or live chat applications.
However, using WebSockets solely to push server status updates to a client introduces unnecessary complexity:
- Protocol Overhead: WebSockets switch protocols from HTTP to WS via an initial handshake, bypassing standard HTTP features like caching, compression, and native routing.
- Infrastructure Complexity: Managing WebSocket connections across scaled server fleets requires sticky sessions, Redis pub/sub backplanes, or dedicated socket gateways.
- Proxy and Firewall Issues: Corporate proxies and firewalls frequently drop idle or non-standard WebSocket connections.
If your client only needs to receive updates from the server without sending high-frequency messages back, WebSockets add structural friction without proportionate benefits.
Enter Server-Sent Events (SSE)
Server-Sent Events (SSE) is a standard browser API built on top of plain HTTP. It enables a server to push real-time data streams to client applications over a single, persistent HTTP connection.
Unlike WebSockets, SSE is strictly unidirectional: data flows exclusively from the server to the client. The client initiates standard HTTP GET request with a specialized Accept: text/event-stream header, and the server responds by keeping the connection open and streaming formatted data chunks whenever new events occur.
Key Advantages of SSE
- Native Browser Support: Uses standard HTTP/1.1 or HTTP/2 without custom protocol upgrades.
- Automatic Reconnection: Browsers automatically attempt to reconnect if the connection drops, sending a
Last-Event-IDheader so the server can resume missed messages seamlessly. - Built-in Event ID and Typing: SSE supports custom event names and message IDs out of the box.
- Simple Infrastructure: Fits seamlessly into existing HTTP load balancers, authentication middleware, and API gateways.
Implementing SSE: A Practical Example
Let's look at how straightforward it is to implement SSE using Node.js/Express on the server and modern JavaScript on the client.
Server Implementation
On the backend, you configure standard response headers and keep the connection open:
// server.js (Express example)
app.get('/api/live-status', (req, res) => {
// Set headers required for SSE
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Send an initial greeting or connection confirmation
res.write('data: {"message": "Connected successfully"}\n\n');
// Send periodic updates when data changes
const intervalId = setInterval(() => {
const payload = JSON.stringify({ timestamp: Date.now(), status: 'active' });
res.write(`data: ${payload}\n\n`);
}, 5000);
// Clean up when client disconnects
req.on('close', () => {
clearInterval(intervalId);
res.end();
});
});
Notice the format: each message begins with data: and ends with two newline characters (\n\n).
Client Implementation
In the browser, consuming the stream requires just a few lines of code using the native EventSource API:
// client.js
const eventSource = new EventSource('/api/live-status');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received real-time update:', data);
updateDashboardUI(data);
};
eventSource.onerror = (error) => {
console.error('SSE connection error:', error);
// Browser automatically retries connection
};
If you need custom HTTP headers for authentication (such as Bearer tokens), you can use the browser fetch API combined with a ReadableStream instead of EventSource.
Essential Considerations and Best Practices
While SSE is remarkably clean, keep these architectural practices in mind:
- HTTP/1.1 Connection Limits: Browsers limit HTTP/1.1 connections to 6 per domain. If a user opens multiple browser tabs, connections can become exhausted. Solution: Serve SSE over HTTP/2, which supports multiplexing hundreds of streams over a single connection.
- Heartbeats and Timeouts: Intermediary proxy servers or cloud load balancers may terminate idle HTTP connections after 30 to 60 seconds. Send a lightweight comment heartbeat (e.g.,
: ping\n\n) every 15–20 seconds to maintain connection health. - Graceful Shutdown: Ensure your backend registers cleanup handlers on socket close events to prevent memory leaks and dangling interval timers.
Summary
HTTP polling is a temporary convenience that quickly scales into an engineering bottleneck. WebSockets excel for interactive two-way communication, but add infrastructure overhead when you only need server-to-client updates.
For unidirectional real-time feeds—whether it's tracking background job progress, live data dashboards, or notification systems—Server-Sent Events provide the ideal middle ground: native, efficient, resilient, and built directly on standard HTTP.
Next time you consider writing a setInterval fetch loop, replace it with an SSE endpoint instead. Your servers and users will thank you.

Responses
Loading comments…