Use server-sent events (SSE) when the server only needs to push updates to the browser, WebSockets when both sides need to send messages freely, and polling when updates are rare or your infrastructure is simple. SSE is the quiet winner for many modern apps, including AI chat streaming, because Anthropic’s and OpenAI’s streaming APIs both use it. WebTransport, a newer option built on HTTP/3, reached Baseline browser support in March 2026. This guide compares the four approaches on direction, reconnection, connection limits, data types, and scaling, with code from MDN.
The Four Options at a Glance
Polling means the browser asks the server for news on a timer. Long polling means the server holds the request open until it has something to say. SSE keeps one HTTP response open and streams events down it. WebSockets upgrade the connection into a two-way channel. WebTransport is a newer two-way API with multiple streams and optional unreliable datagrams.
Polling and Long Polling
How They Work
Plain polling repeats a request at a fixed interval. RFC 6202, the IETF’s informational document on bidirectional HTTP, describes long polling: the server holds the request until an event or a timeout, then the client immediately asks again. The RFC lists drawbacks, including full HTTP headers on every message, added latency, and a held TCP connection and request for each client. It also notes that for HTTP streaming, intermediary proxies may buffer the response, and messages need application-level framing.
When Polling Is Fine
If updates come every few minutes, a dashboard that refreshes every 30 seconds is simpler than any persistent connection. It works through every proxy and needs no special server.
Server-Sent Events
What SSE Is
MDN describes SSE as one-way: “you can’t send events from a client to a server.” The server answers with the MIME type text/event-stream, the stream is UTF-8 text, and messages are separated by a blank line. MDN says SSE has been Baseline, meaning available across browsers, since January 2020.
The Client Code
MDN’s example is short:
const evtSource = new EventSource("sse-demo.php");
evtSource.onmessage = (event) => { /* event.data */ };
evtSource.addEventListener("ping", (event) => {
const time = JSON.parse(event.data).time;
});
evtSource.onerror = (err) => {
console.error("EventSource failed:", err);
};
evtSource.close();
The Server Side
MDN’s PHP example sets three headers: X-Accel-Buffering set to no, Content-Type set to text/event-stream, and Cache-Control set to no-cache. It then writes event lines and flushes, for example an event named ping followed by a data line of JSON and a blank line. On the wire a message looks like an event line, a data line, and an empty line.
Reconnection for Free
MDN says that “By default, if the connection between the client and server closes, the connection is restarted.” A retry field sets the delay in milliseconds. The WHATWG HTML standard adds that on reconnection the client sends the last event ID in a Last-Event-ID request header, which lets a server resume where it left off, and that the default delay is implementation-defined, “probably in the region of a few seconds.” The standard also says HTTP 204 tells the client to stop reconnecting, while a non-200 status or a wrong content type fails the connection with no retry.
Keep Idle Connections Alive
WHATWG advises sending a comment line, starting with a colon, “every 15 seconds or so” so proxies that drop idle connections do not close the stream.
The Six-Connection Catch
MDN warns that without HTTP/2, SSE “suffers from a limitation to the maximum number of open connections… per browser… a very low number (6),” and says this is marked “Won’t fix” in Chrome and Firefox. With HTTP/2, the number of simultaneous streams is negotiated between server and client and defaults to 100. This matters if a user opens many tabs.
Limits
SSE is text only, in UTF-8. Clients cannot send messages over the same stream, so they use ordinary requests for that.
WebSockets
What They Are
MDN defines the WebSocket API as “a two-way interactive communication session between the user’s browser and a server.” Either side can send at any time.
The Client Code
const websocket = new WebSocket("ws://127.0.0.1/");
websocket.addEventListener("open", () => {
websocket.send("ping");
});
websocket.addEventListener("message", (e) => {
console.log(e.data);
});
websocket.addEventListener("close", () => {
console.log("DISCONNECTED");
});
websocket.close();
The Handshake
RFC 6455 shows a handshake that starts as an ordinary HTTP request with an Upgrade: websocket header and a Sec-WebSocket-Key. The server replies with HTTP/1.1 101 Switching Protocols and a Sec-WebSocket-Accept value, computed by appending a fixed GUID to the key, hashing it with SHA-1, and encoding it in base64. The protocol uses port 80 for ws and 443 for wss. Clients must mask every frame they send, and servers must not mask frames they send. Ping, pong, and close frames use opcodes 0x9, 0xA, and 0x8.
Heartbeats and Binary Data
MDN says either side can send a ping and the other must answer with a pong carrying the same payload, which you can use “to make sure that the client is still connected.” Control frame payloads are at most 125 bytes. Servers can send binary data, exposed to browsers as a Blob or ArrayBuffer, and clients can send Blob, ArrayBuffer, typed arrays, or DataView objects.
No Automatic Reconnection
The sources reviewed do not describe automatic reconnection for WebSockets, in contrast with SSE. Plan to write your own reconnection logic with backoff, and resend state after reconnecting.
No Backpressure
MDN notes that the WebSocket API has no backpressure. If messages arrive faster than your app processes them, memory can fill or the CPU can pin. WebSocketStream, a streams-based alternative, is “non-standard and currently only supported in one rendering engine.”
Infrastructure Notes
MDN says WebSocket servers are often separate specialized servers behind a reverse proxy, and that ports other than 80 and 443 “may have problems with firewalls and/or proxies.” RFC 8441 defines running WebSockets over a single HTTP/2 stream with an extended CONNECT method. Scaling WebSockets generally means keeping many stateful connections open, so plan for load balancer behavior, but this research found no authoritative figures, so test with your own traffic.
WebTransport: The Newcomer
MDN describes WebTransport as “a modern update to WebSockets” built on HTTP/3 over QUIC. It supports multiple streams, unidirectional streams, reliable streams, and unreliable datagrams. It requires HTTPS with an explicit port, a supporting server, and origin verification. MDN labels it “Baseline 2026 Newly available,” since March 2026. Caniuse lists Chrome 97, Edge 98, Firefox 114, and Safari 26.4 or later, with 91.64 percent global usage. The basic call is short:
const transport = new WebTransport("https://example.com:4999/wt");
await transport.ready;
Because the server side is less common, WebTransport suits cases where datagrams or many parallel streams matter, such as games or media.
The 2026 Angle: AI Streaming Uses SSE
Anthropic’s documentation says to set stream to true to receive server-sent events, in an order that starts with message_start, then content block start, delta, and stop events, then message_delta and message_stop. Ping events can appear anywhere, and mid-stream errors arrive as an error event, such as overloaded_error. OpenAI’s Responses API streaming guide describes stream set to true as HTTP streaming over SSE, with typed events such as response.created, response.output_text.delta, response.completed, and error, and mentions a separate WebSocket mode for persistent connections. The pattern fits SSE well: one request, a long one-way stream of tokens, and a client that never needs to talk back on the same connection.
A historical example points the same way. A 2011 web.dev case study of Stream Congress from the Sunlight Foundation chose EventSource because the app “only needs to stream updates one way, from server to client,” and called WebSockets “overkill.” It is old, so treat it as an illustration.
How to Choose
Pick Polling When
Updates are infrequent, you want the simplest possible server, or your network blocks long-lived connections.
Pick SSE When
The server pushes and the client only listens: notifications, live scores, progress bars, logs, and AI token streams.
Pick WebSockets When
Both sides send often and with low latency: chat, collaborative editing, multiplayer games, or binary protocols.
Pick WebTransport When
You need unreliable datagrams or many independent streams and can rely on HTTP/3 support.
Common Mistakes
Do not choose WebSockets by default. Do not forget heartbeats or keepalive comments. Do not ignore reconnection and state recovery. Do not serve SSE over HTTP/1.1 to users who open many tabs. Do not assume proxies will pass your stream; test through your real load balancer.
A Decision Checklist
Ask five questions. Does the client ever need to send messages other than ordinary requests? If not, SSE is likely enough. How fast do updates need to arrive? If a delay of several seconds is fine, polling is simpler. Do you need binary data? If so, WebSockets or WebTransport. Will users open many tabs? If so, make sure SSE runs over HTTP/2 to avoid the six-connection limit MDN describes. Do you control the proxies and load balancers in front of your server? If not, test for buffering, since RFC 6202 notes that proxies may buffer streamed responses and the WHATWG standard advises periodic comment lines for idle connections.
Testing Real-Time Features
Test the failure paths more than the happy path. Kill the server and watch the client reconnect. Drop the network on a phone and bring it back. Send events faster than the client can handle, which is where MDN’s note about WebSocket backpressure applies. Leave a connection idle through a proxy for several minutes to see whether it is dropped. For SSE, check that the Last-Event-ID header arrives and that your server can resume from it. These tests find more bugs than any benchmark.
Security Considerations
The sources reviewed do not cover authentication for these protocols in detail, so verify current guidance before shipping. As general practice, serve everything over HTTPS (wss for WebSockets), since MDN notes that WebTransport requires HTTPS and an explicit port. Validate the Origin header where the protocol exposes it, as MDN says WebTransport servers must do. Treat every incoming message as untrusted input.
Putting It Together: Three Example Choices
A live sports score ticker pushes numbers to many viewers and never needs viewers to send data back, so SSE fits. A collaborative whiteboard needs every participant to send and receive changes quickly, so WebSockets fit. A dashboard that refreshes inventory every minute is fine with polling. An AI chat interface streaming tokens uses SSE for the response and ordinary requests for the user’s message, which matches how the Anthropic and OpenAI APIs are documented.
Frequently Asked Questions
Is SSE better than WebSockets?
For one-way updates, often yes, because it is simpler and reconnects automatically. For two-way messaging, WebSockets fit better.
Does SSE support binary data?
No. It is UTF-8 text only.
Can SSE reconnect automatically?
Yes. Browsers restart the connection by default and send a Last-Event-ID header.
What is WebTransport?
A newer HTTP/3-based API with multiple streams and datagrams, Baseline since March 2026.
What do AI chat APIs use?
Anthropic and OpenAI document server-sent events for streaming responses.
References and Sources
MDN, “Using server-sent events”: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
WHATWG, “HTML Living Standard, Server-sent events”: https://html.spec.whatwg.org/multipage/server-sent-events.html
MDN, “WebSockets API”: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
MDN, “Writing WebSocket servers”: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers
MDN, “Writing WebSocket client applications”: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications
IETF, “RFC 6455: The WebSocket Protocol”: https://www.rfc-editor.org/rfc/rfc6455
IETF, “RFC 8441: Bootstrapping WebSockets with HTTP/2”: https://www.rfc-editor.org/rfc/rfc8441
IETF, “RFC 6202: Known Issues and Best Practices for the Use of Long Polling and Streaming in Bidirectional HTTP”: https://www.rfc-editor.org/rfc/rfc6202
MDN, “WebTransport API”: https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API
Can I use, “WebTransport”: https://caniuse.com/webtransport
Anthropic, “Streaming Messages”: https://platform.claude.com/docs/en/build-with-claude/streaming
OpenAI, “Streaming API responses”: https://developers.openai.com/api/docs/guides/streaming-responses
web.dev, “Case Study – Real-time Updates in Stream Congress”: https://web.dev/case-studies/sunlight-streamcongress
