Real-Time APIs: WebSockets, Server-Sent Events, and Long Polling
Standard HTTP is a request-response protocol: the client sends a request, the server sends a response, the connection closes or is returned to a pool. This model is efficient for most API use cases. It is the wrong model when the server needs to push data to the client without waiting for a client request — live dashboards, chat applications, collaborative editing, real-time notifications, trading feeds. Three patterns exist to bridge this gap, each with a different complexity profile and a different set of constraints.
Long Polling: The Compatibility Approach
Long polling is regular HTTP used cleverly. The client sends a request. Instead of responding immediately, the server holds the request open until it has new data to send. When new data arrives, the server responds and closes the request. The client immediately sends a new request. From the user’s perspective, the experience is nearly real-time. From the infrastructure’s perspective, you have long-lived HTTP connections using standard web server machinery.
The implementation is simple: the server needs to hold open connections, which most modern frameworks handle through async or event-driven patterns. No new protocols, no new infrastructure, no WebSocket support required. Every HTTP client supports long polling because it is just HTTP.
The inefficiencies are structural. Each data delivery involves a complete HTTP request-response cycle, including headers, connection management, and any middleware overhead. Under high message frequency, the overhead-to-payload ratio degrades. There is also a gap: the moment between when the server responds and when the client sends its next request during which no new data can be delivered. For low-frequency updates, this gap is irrelevant. For high-frequency streams, it becomes a problem.
Long polling is the right choice when real-time-ish behavior is needed but WebSocket support is absent or unreliable — legacy environments, restrictive corporate proxies, or clients where persistent connections are not supported. It is also a reasonable default for low-frequency notifications where the architectural simplicity outweighs the efficiency cost.
Server-Sent Events: One-Way Streaming Over HTTP
Server-Sent Events (SSE) is an HTTP-based protocol for one-way streaming from server to client. The client makes a standard HTTP GET request, and the server responds with Content-Type: text/event-stream and keeps the connection open, sending data as newline-delimited events:
data: {"type": "price_update", "symbol": "AAPL", "price": 195.42}\n\n
data: {"type": "price_update", "symbol": "GOOGL", "price": 172.18}\n\n
The browser’s EventSource API handles reconnection automatically — if the connection drops, the browser reconnects after a brief delay. The server can include an id field in events; the browser sends the last received ID in the Last-Event-ID header on reconnect, allowing the server to resume the stream from where the client left off rather than starting over.
SSE runs over standard HTTP/1.1 or HTTP/2, works through standard proxies and load balancers (with appropriate timeout configuration), and is supported natively in every modern browser via EventSource. The protocol is simple enough to implement in minutes on most server frameworks.
The constraint is directionality: SSE is server-to-client only. The client cannot send data over the SSE connection. For use cases where the server needs to push data to the client but the client communicates through separate REST API calls — notifications, live dashboards, progress updates, AI response streaming — SSE is the cleanest option. For bidirectional communication, it is not sufficient.
SSE has become particularly prevalent for streaming LLM responses. An API that generates text token-by-token can stream each chunk to the client via SSE, producing the appearance of the text being written in real time rather than delivered as a complete block after a long wait. This is how most AI chat interfaces work.
WebSockets: Full-Duplex Communication
WebSockets establish a persistent, full-duplex connection between client and server over a single TCP connection. Either side can send messages at any time without a request-response cycle. Latency is lower than any HTTP-based approach because there are no request headers on each message — once the connection is established, messages are small framed packets.
The WebSocket connection begins with an HTTP upgrade handshake:
GET /ws HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
The server responds with 101 Switching Protocols, and the connection transitions from HTTP to the WebSocket protocol. From this point, both sides can send messages in any direction, at any time, with low overhead.
WebSockets are the right choice for genuinely bidirectional real-time communication: multiplayer games, collaborative document editing, trading platforms where the client sends orders and receives live price updates on the same connection, chat applications where the same connection handles sends and receives. They are also useful when the client needs to send high-frequency messages (position updates, sensor data) that would be expensive to send as individual HTTP requests.
The operational costs are real. WebSocket connections are stateful and long-lived. They do not work naturally behind standard HTTP load balancers without sticky session configuration or a shared message bus (like Redis pub/sub) that all server instances subscribe to. Reconnection logic, error handling, and connection state management are the client’s responsibility — the EventSource auto-reconnect that SSE provides for free must be built manually for WebSockets. Proxies, firewalls, and corporate networks that are configured to block non-HTTP traffic will break WebSocket connections, though this is less common than it was.
WebSockets are often over-used. Many applications that reach for WebSockets because they need “real-time” actually need SSE (server pushes to client, client communicates via REST) or even just polling (updates every 30 seconds). The additional complexity of WebSockets pays off only when true bidirectional low-latency communication is required.
HTTP/2 Server Push: The Path Not Taken
HTTP/2 included a server push feature that was meant to allow servers to push resources to clients proactively. It never achieved widespread adoption due to implementation complexity and the difficulty of knowing what to push. Most browsers have deprecated or removed support for HTTP/2 server push. It is not a viable option for real-time APIs.
Choosing
The decision tree is straightforward. If you need the server to push data to the client and the client communicates via standard API calls: use SSE. It is simpler to implement than WebSockets, works through more infrastructure without configuration, handles reconnection automatically, and is sufficient for the vast majority of “real-time” use cases that are actually one-directional server push.
If you need bidirectional low-latency communication where both sides initiate messages on the same connection: use WebSockets. Accept the operational complexity in exchange for the capability.
If neither SSE nor WebSockets is available or appropriate for the deployment environment: long polling is a reliable fallback that works everywhere HTTP works.
The temptation to use WebSockets for everything real-time is common and usually wrong. SSE handles more cases than developers initially expect. Reach for WebSockets when you can clearly articulate why the bidirectional nature of the connection is necessary, not because it sounds more technically sophisticated.