HTTPIntermediate

WebSockets vs Server-Sent Events

“You need to push updates from a server to a browser. Compare long polling, Server-Sent Events and WebSockets: what does each cost at the network layer, what breaks with each in production, and how do you choose?”

What this tests

  • The wire mechanism of each option, not just "real time"
  • Direction, framing and connection-count constraints
  • Proxy, load-balancer and idle-timeout interaction
  • Scaling stateful connections across a fleet

Answers by level

Read the beginner answer first and notice what is missing.

Long polling is ordinary HTTP with the response delayed: the client sends a request, the server holds it until an event or a timeout (~30 s), then the client immediately sends another. Every event costs a full request/response with headers, one held connection per client, and a gap between events during which nothing can arrive. It works through every proxy and cache because it *is* HTTP, and it is the right answer for infrequent events and hostile networks.

Server-Sent Events are one long-lived HTTP response: Content-Type: text/event-stream, a streamed body of data: lines with optional id: and retry: fields. Server-to-client only, text only (UTF-8). The browser’s EventSource reconnects automatically and sends Last-Event-ID, so resume-after-drop comes for free. Because it is a normal response it passes through load balancers and TLS termination unchanged — but any proxy that *buffers* responses (nginx by default, proxy_buffering off or X-Accel-Buffering: no to fix) turns a stream into a batch, and any idle timeout (ALB 60 s) kills a quiet stream unless the server sends a comment line (: ping) every 15–30 s. Over HTTP/1.1 each stream is a connection and browsers cap ~6 per origin, so six tabs starve the seventh; over HTTP/2 streams are multiplexed and the cap disappears. See Polling vs Long Polling vs SSE vs WebSockets.

WebSockets begin as an HTTP/1.1 request with Upgrade: websocket and a Sec-WebSocket-Key; the server answers 101 Switching Protocols and from then on the same TCP connection carries a framed, bidirectional, binary-capable protocol with a 2–14-byte header per message — no HTTP headers per message, sub-millisecond client-to-server sends. The costs: every proxy and balancer in the path must understand the upgrade (nginx needs explicit Upgrade/Connection headers forwarded; classic HTTP-mode ELBs did not support it), HTTP/2 needs the extended CONNECT of RFC 8441 which is unevenly supported, there is no automatic reconnect or resume — you build heartbeats (ping/pong frames), backoff with jitter and a resume protocol yourself — and the connection is stateful, so a user’s session lives on one node. See WebSockets.

Choose by direction and frequency. Server-to-client feeds — dashboards, notifications, streaming LLM tokens, progress — are SSE: simplest, cache- and proxy-friendly, free resume. Genuinely bidirectional low-latency traffic — collaborative editing, chat with typing indicators, games, terminals — is WebSockets. Rare events or clients behind unknown proxies: long polling. Whichever you pick, the persistent connection count is the capacity unit: each is an fd and kernel buffers on the server, and a deploy that restarts the fleet reconnects *every* client at once.

Green flags · Red flags

Strong green flag · Picks SSE for server-to-client feeds without prompting and explains what WebSockets would add and cost in that specific case.
Green flags
  • Describes each mechanism on the wire: held request, streamed response, upgraded framed connection
  • Knows SSE is one-directional text with automatic reconnect and Last-Event-ID
  • Mentions proxy buffering, idle timeouts and the Upgrade requirement
  • Names the HTTP/1.1 six-connection limit and HTTP/2’s effect on it
  • Explains that persistent connections shift the scaling problem to fan-out and fd count
Red flags
  • Picks WebSockets by default for everything
  • Does not know SSE exists or thinks it needs a library
  • Unaware that proxies and load balancers can break either one
  • Has no plan for reconnection or for routing a message to the right node

Follow-up questions

F1
An SSE endpoint works locally but in production events arrive in bursts every few seconds. Why?
F2
A WebSocket-based app drops all connections every time you deploy. What do you change?
F3
Why does a browser open an SSE stream in one tab and the sixth tab hang on every request?

Scenario

A product wants live order-status updates in a web dashboard: one update per order every few seconds, thousands of concurrent viewers, occasional "cancel" actions from the viewer. Choose a transport, justify it at the network layer, and list the three production settings you would check before launch.

Learn this topic