Long Polling vs Server-Sent Events: Choosing a Push Strategy
Long polling vs SSE compared: how server sent events work, where websockets fit, sse vs polling costs, and a decision table for choosing a push strategy over plain HTTP.
This article is the second half of the real-time communication pair. The first, what are websockets, covered the persistent connection (full-duplex, stateful, alive) and kept deferring one question to here: when the server talks more than it listens, is the honest answer really an upgraded connection, or can plain HTTP carry the load? The client server architecture foundations framed the problem precisely (the server cannot start a request response cycle) so every push strategy is a workaround, and workarounds deserve comparison before adoption.
Both strategies stay inside HTTP, which is the whole point. Long polling asks the server a question and refuses to leave without an answer: the server holds the request open until data exists (or a timeout passes) responds, and the client immediately asks again. Server-sent events (SSE) dedicate a single response to a stream: the server keeps the connection open and writes events as they happen, one line of protocol at a time. Neither needs an upgrade handshake, exotic infrastructure, or a new failure model; both work through the proxies and balancers already in place, which is why this pair’s prerequisite reading is the reverse proxy and the price of each round trip in latency vs throughput.
The choice is not which technology is better; it is which direction the data flows and how fresh it must be. Push that is mostly one-way (server to client) with occasional client writes fits SSE, which has the cleaner protocol and built-in reconnection. Push over infrastructure that distrusts streaming fits long polling, which is indistinguishable from ordinary traffic and degrades gracefully. And genuinely two-way continuous conversation belongs to websockets; the option this article compares against but does not re-teach. What follows prices each strategy honestly, then decides.
What follows defines both primitives and their shared job, puts the comparison in a decision table, then takes the three questions this cluster assigns to this article in order: what server sent events actually are on the wire, where websocket vs sse lands, and what sse vs polling costs at each rung of the ladder. The websockets section keeps to the boundary the first article set: mechanics there, strategy here.
What is long polling vs sse
Long polling is a request that waits. The client sends an ordinary HTTP GET; the server, instead of answering immediately or answering “nothing new,” holds the request open (seconds, sometimes a minute) until an event exists to return, then answers and closes. The client receives the answer and asks again, so the cycle repeats forever: from the outside it is an oddly patient browser; from the inside it is push built entirely out of the request response cycle’s own parts.
Server-sent events are a response that never ends, deliberately. The client requests a stream once; the server answers with Content-Type: text/event-stream and then keeps writing events in a tiny text protocol: an id line, an event name, and the data, each separated by a blank line. The connection stays open; the server writes whenever there is something to say; and the browser’s EventSource parses, dispatches, and, the protocol’s quiet gift, reconnects automatically on disconnect, resuming from the last event id it received.
Both strategies make the server’s silence expensive only once. Short polling (ask, get “nothing,” wait, ask again) pays a full round trip per check and delivers events only as fresh as the polling interval; both long polling and SSE remove the asking from the loop. The difference is the mechanism: long polling rents the request response cycle repeatedly, one held request at a time; SSE amortizes one response into a stream. That single difference drives everything in the table below; resource shape, latency, ordering, and how each fails.
| Dimension | Long polling | Server-sent events |
|---|---|---|
| Architecture | Held HTTP requests, one per pending event; ordinary request-response parts only | One long-lived streaming response per client; text/event-stream protocol |
| Consistency | Each response is one event; ordering per client, but no resumption across reconnects by default | Event ids make the stream resumable; Last-Event-ID replays missed events |
| Complexity | Minimal client code; server must hold and time out many open requests | EventSource client built into browsers; server writes events to held streams |
| Failure behavior | Timeouts and dropped holds look like failed requests; the client asks again, self-healing but blind | Auto-reconnect built into the protocol; missed events recoverable via event ids |
| Best fit | Patchy infrastructure, low event rates, or stacks where streaming is a fight | One-way feeds (dashboards, notifications, ticks, logs) over infrastructure that streams cleanly |
Read the table as a shape, not a verdict: long polling is HTTP everywhere and costs an open request per client per event window; SSE is a cleaner wire protocol but asks the infrastructure chain to let one response live for hours. The sections below expand what the table compresses (the protocol itself, the websocket comparison, and the polling ladder) so the decision lands on the workload, not on preference.
The inputs behind the table are worth naming, because they are the same four for every push decision. Event rate: how often the server has something to say, per hour, per minute, per millisecond. Direction: whether the client ever needs to talk at stream speed. Infrastructure trust: whether the chain between client and server will carry a held request or an endless response without helpfully breaking it. Client population: a thousand dashboards behave differently from a million phones. Every row in the table is one of these inputs wearing a dimension’s name, which is why the comparison resolves into engineering and not preference.
Server sent events
The wire format deserves a look, because its smallness is the design. An event is a few lines: id: 42 to name the event’s position in the stream, event: price-change to type it, data: {"price": 19.42} to carry it, and a blank line to end it. The client’s EventSource fires a typed event per message; JSON, plain text, or anything parseable rides in data. There is no envelope, no framing negotiation, no binary mode; a workload that needs binary payloads or very large messages is already voting for a different tool.
The two built-in behaviors matter more than the format. Auto-reconnect: when the stream drops, EventSource reconnects on its own (with backoff) and presents the last id it saw, so the server can replay the gap. A push protocol that recovers its own missed messages is rare; polling has nothing like it. Heartbeat: the server typically writes a comment line (or a retry: hint) on an interval, to keep proxies and balancers from timing the stream out: the same dead-peer problem websockets solve with pings, solved here with a line of text.
The limits are equally clear. SSE is one-way; client-to-server writes need ordinary requests beside the stream, which is fine for “send a message” buttons and poor for high-frequency client telemetry. Browsers cap the number of concurrent streams per origin, six on HTTP/1.1, the same limit that applies to any parallel responses, which pushes large deployments onto HTTP/2, where many streams share one connection, or onto subdomain sharding. And the server holds one stream per client: the scaling math is the websocket article’s math in a milder key, with the same fan-out backplane question the moment more than one server is involved.
On the server side, the implementation shape is honest about the real work: one writer loop per stream (writes to a client must be serialized; two threads writing one socket interleave bytes, and the protocol does not forgive it), a registry of streams by client, and a fan-out routine that pushes each new event to every matching stream. The moment the fleet has more than one server, that fan-out crosses machines and becomes the message queue backplane pattern from the websockets article, with the same delivery-guarantee questions, because an event lost in fan-out is lost from every listener on that server. SSE does not remove the distributed problem; it just shrinks the connection layer of it.
Websocket vs sse
The bidirectional comparison starts from what each side gives up. Websockets offer full-duplex frames, binary payloads, and one connection for everything, at the price the first article priced: an upgrade handshake that must survive every intermediary, stateful connections that pin routing, and an ecosystem (auth, reconnection, delivery) the application builds itself. SSE offers one direction, text payloads, and a protocol the browser already speaks, with reconnection and resumption in the box. The choice between them is rarely “which is better” and almost always “is the client’s traffic actually two-way?”
When it is: chat, collaborative editing, games, websockets win without argument; SSE cannot carry the return channel at stream speed. When it is not: dashboards, notifications, price ticks, logs, progress bars; SSE wins on the same certainty: the client does not need to talk fast, so paying websocket prices for a one-way feed buys statefulness, upgrade fragility, and custom reconnection code that the browser would have provided for free. A hybrid is also honest: SSE for the feed, ordinary requests for the client’s writes, a shape many production notification systems settle on.
One infrastructure point decides more deployments than any protocol argument: SSE is plain HTTP, so it passes through proxies, gateways, and CDNs that handle long responses well, and trips only where buffering is on by default (a buffered text/event-stream is a broken stream; the reverse proxy article’s buffering toggle is the fix). Websockets need every hop to forward the upgrade and stop buffering. The API gateway dilemma applies to both (an idle stream is an idle request) but SSE’s streams at least speak the grammar gateways were built for.
Authentication differs in one practical way. An SSE stream begins as an ordinary GET, so it authenticates the way every other request does (cookies, Authorization headers, whatever the stack already trusts) and inherits whatever that posture already got right. A websocket’s upgrade is also an HTTP request and can carry the same credentials, but the moment the connection is established it outlives the request that authorized it, so revocation, expiry, and re-authentication mid-connection become the application’s problem. Neither is insecure by nature; SSE is simply the one that stops making security decisions after the first request.
Sse vs polling
The ladder from polling to SSE is a ladder of asking. Short polling asks on a timer: every check costs a round trip, and events arrive only as fresh as the interval, with the choice’s own cruel arithmetic, since fresher means more requests. Long polling asks once per event window and holds: the round trip is paid per answer rather than per check, and each event arrives as soon as it exists. SSE asks once, period: after the initial request, the server writes events as they happen, and the only cost left is the held stream.
The costs concentrate in different places. Short polling spends client effort and server request-handling on mostly empty checks; the “no news” answers are the budget. Long polling converts that into held requests: fewer wasted checks, but the server now owes per-client timers, timeout logic, and the bookkeeping of who is waiting for what: work proportional to waiting clients, not to events. SSE’s cost is the held stream itself, file descriptors and memory per client: the same shape as websockets’ idle connections, without the upgrade dependencies.
Where the ladder breaks is instructive. Long polling’s held requests collide with server request limits (a thread-per-request server runs out of threads long before it runs out of events) and with balancer timeouts tuned for ordinary traffic; the load balancer treats a patient request as a stuck one. SSE’s streams collide with response buffering and idle timeouts in the chain. Both fail at the infrastructure layer, not the protocol layer, which is the deepest argument for testing the chain before choosing, and the reason “it worked in development” is the least reassuring sentence in real-time engineering.
The scaling shapes are the last honest comparison. Short polling’s cost scales with the checking rate; it is the only strategy whose cost is set by the client’s impatience rather than the server’s news. Long polling’s scales with the waiting population and the event rate together, one held request per waiting client per event window. SSE’s scales with the client count alone: one stream per client, plus one write per event per interested stream. A workload with rare events and many clients drifts toward SSE; a workload with frequent events and few clients can afford to poll anything; and the crossover between the two held strategies is where the real design conversation lives.
Common mistakes
- Polling on a fixed short interval. A five-second poll is either too slow for the events that matter or too expensive for the ones that do not, and usually both at different hours. If the interval is a latency budget, the strategy is wrong; that is what the held-request designs exist for.
- Forgetting the balancer timeout. A balancer that cuts idle requests at 30 seconds will chop long polls mid-wait and streams mid-flow, and the client reads it as flaky push. Align the held-request timeout, the balancer’s idle timeout, and the SSE heartbeat interval; they are one number wearing three names.
- Buffering the stream. A proxy that buffers responses turns SSE into “everything arrives at once, every few minutes.” The stream must be flushed per event, and the fix belongs in the proxy configuration, not in application code fighting it with padding.
- Assuming delivery across reconnects. Long polling with no cursor and SSE with no event ids both lose whatever the disconnect swallows. Reconnection is protocol; resumption is design; give events ids, remember the last one the client acknowledged, and replay the gap with the client handling replays as the idempotency article anchors.
- Choosing websockets because “real-time.” A feed that never receives client traffic does not need a bidirectional connection; it needs the cheapest held stream that survives the infrastructure. Reserve the upgraded connection for conversations that are genuinely two-way; the first article’s boundary rule holds here in reverse.
FAQ
Is long polling still used today?
Yes, less by fashion than by necessity. It survives where infrastructure (corporate proxies, older gateways, restrictive CDNs) breaks streaming or upgrades, and in systems that want push with zero new moving parts: it is ordinary HTTP, indistinguishable from other traffic, which is sometimes the deciding property.
What is the difference between SSE and long polling?
Long polling rents the request-response cycle per event: one held request, one answer, ask again. SSE holds one response open and streams events inside it: one request, many answers, in order, with ids and built-in reconnect. Polling is more compatible; SSE is cleaner and cheaper per event.
Does SSE work over HTTP/2?
Yes, and it works better: HTTP/2 multiplexes many streams over one connection, so the browser’s per-origin stream limits stop being the wall they are on HTTP/1.1. The event-stream protocol itself is unchanged; HTTP/2 changes how many streams share the connection, not what they say.
Can the client send messages with SSE?
Not over the stream; it is server-to-client by design. The standard shape is SSE for the feed and ordinary HTTP requests for the client’s writes, which covers most “server mostly talks” workloads without ever upgrading a connection.
Which should I use for notifications?
SSE, if the infrastructure streams cleanly and the feed is one-way: reconnect and resumption are in the box, and the client code is small. Long polling, if the chain in between is hostile or untested, it degrades into ordinary traffic everywhere. Websockets, if notifications are only one part of a two-way conversation.
Related articles
- Next read: l4 vs l7 load balancing; the question every held request and open stream eventually raises: when connections pile up, where does the balancer look; connection or request, and what can each one see?
- what are websockets; the first half of this pair: the persistent, full-duplex connection whose costs and mechanics this article kept comparing against.
- reverse proxy, the buffering and timeout decisions that decide whether a stream or a held request survives the chain between client and server.
- latency vs throughput; the price of each round trip the ladder of asking is built from, and why fresher polling means more requests.
- API gateway; the piece that meets held requests as worker-thread hostages and streams as never-ending responses: what gateways must do before push strategies pass through them.
Last updated on 2 September 2026