skip to content

Server-Sent Events (SSE)

12 roadmaps33 questionsupdated

One-way event streaming over an ordinary HTTP response: a text grammar, per-event ids, and reconnection for free. Interviewers use it to see whether you reach for a socket where a stream would do.

on this pageshow

guide

overview

~1 min

Server-Sent Events (SSE) is a way to push a stream of messages from server to client inside one long-lived HTTP response. The body is plain UTF-8 text in a tiny line-based grammar, each message can carry an id, and a browser client reopens the stream on its own after a drop, telling the server where it left off. Interviewers use it to test judgment more than syntax: can you tell when a one-way stream is enough, and do you know what breaks once a real network path sits between emitter and reader? It is also the usual transport for streaming LLM output, so it shows up in AI product rounds too. The hub follows one stream from bytes to production. [Wire Format](/topics/proto-sse-wire-format) is the grammar itself, and [Client Tolerance Rules](/topics/proto-sse-eventsource-api) is what a conforming reader does with malformed or unexpected input. [Retry and Resumption](/topics/proto-sse-reconnect-lastid) covers how a dropped stream picks up again. [Holding the Response Open](/topics/proto-sse-server-impl) is the emitting side's job, and [Proxy and Buffering Pitfalls](/topics/proto-sse-proxy-pitfalls) is what the path in between does to it. [Authenticating the Stream](/topics/proto-sse-auth) deals with a credential that can expire mid-stream, and [SSE vs WebSockets](/topics/proto-sse-vs-websockets) is the design-round comparison. Junior questions expect exact answers about the format. Senior and principal ones become incident and policy questions: why a feed arrives in clumps, why clients never returned after an outage, how long a stream should live. Learn the grammar and the reconnect loop first; the rest follows from them.

primer

### A response that never finishes An SSE endpoint answers one GET with a success status and the `text/event-stream` media type, then keeps writing. The transport is ordinary HTTP whose body happens to arrive over minutes or hours. That explains both its strengths — every tool on the path understands it — and its failures, since every tool on the path may also buffer, compress or cut it. ### A grammar small enough to know exactly The body is lines of `field: value`. Four field names matter — `data`, `event`, `id`, `retry` — a line starting with a colon is a comment, and an empty line is what turns the accumulated fields into one delivered event. Because it is so small, interviewers expect precise answers, including what a parser does with input that breaks the rules. ### Reconnection is built in, resumption is not A browser client reopens a dropped stream by itself, after a delay the server can set, and sends back the last id it saw. Whether that becomes a gapless resume is up to the server: ids must mean something, and there must be a store to replay from. Reconnection also has an off switch — some responses end it for good, which surprises teams during outages. ### The emitter owns liveness Writing an event is not the same as sending it. Any layer between the handler and the socket — framework, compressor, proxy — may hold bytes back. A live stream needs a flush per event, traffic that keeps an idle connection from looking dead, and a deliberate limit on how long one response lives. ### One-way is a feature The stream carries nothing upstream. Many "real-time" features only need the server to talk, and occasional client actions fit as ordinary requests beside the stream. Knowing when that pairing stops being enough is the WebSocket comparison. ### Authorization happens once The server decides who may read when it opens the response. Nothing in the protocol revisits that decision, so a stream's lifetime is also how long an access decision stays in force.

text/event-stream
The media type an SSE response must declare. A client that receives any other type treats the endpoint as unusable rather than trying to parse it.
Event block
The group of field lines between two empty lines. The empty line is what dispatches it; a block never closed that way is not delivered.
data field
The line type that carries payload. Several in one block are joined into a single multi-line value.
event field
An optional name for the event type. Clients subscribe to named types separately; without it, the event arrives as a generic message.
id field
A server-chosen label for an event. The client keeps the latest one and sends it back on reconnect.
retry field
A server instruction setting how many milliseconds the client waits before reopening a dropped stream.
Comment line
A line beginning with a colon. Clients discard it, which makes it the usual keepalive: traffic that keeps a quiet connection from looking idle without delivering an event.
Last-Event-ID
The request header a reconnecting client sends, carrying the most recent event id it received, so the server can replay what was missed.
EventSource
The browser API that opens an event stream, parses it, dispatches events and handles reconnection. It sends only GET and cannot add custom headers.
Replay window
How far back the server can re-send events for a reconnecting client. An id older than the window means a gap the server must signal.

### One stream, end to end A client opens a GET and names the stream it wants. The server authorizes the request, answers with the event-stream media type and cache-defeating headers, and from then on writes blocks as things happen, flushing after each one. In between sit the server's framework and usually a load balancer or reverse proxy, each of which must pass bytes through as they arrive. During quiet periods the server writes comments so the connection does not look idle. When the response ends or the network drops, the client waits the retry delay and repeats the same request, now carrying the last id it saw. The server looks that id up in its replay store, sends whatever the client missed, then continues live. The second connection, on the wire: ```http GET /feeds/pumps HTTP/1.1 Accept: text/event-stream Last-Event-ID: 41 HTTP/1.1 200 OK Content-Type: text/event-stream Cache-Control: no-cache retry: 5000 : keepalive id: 42 event: fault data: pump 3 offline data: pressure falling ``` ### Where the sections attach Each section of the hub is one link in that chain. The grammar and the tolerance rules are the parser's two halves. Resumption is the reconnect loop plus the server's replay store. The server and proxy sections are the path the bytes take. Authentication is the opening request — and the fact that the reconnect repeats it, credential included, without asking the application. The WebSocket comparison asks whether this chain was the right one to build in the first place.

  1. Wire Format →

    The grammar every other section refers to: four fields, comments and the empty line that delivers an event.

  2. Holding the Response Open →

    What the emitting side owes a stream: the right headers, a flush per event, an open connection.

  3. Retry and Resumption →

    How a dropped stream resumes, and why that needs deliberate ids and a replay store.

  4. Proxy and Buffering Pitfalls →

    The failures that appear only after deploy, once proxies, compressors and idle timeouts sit in the path.

  5. Authenticating the Stream →

    Authorizing a response that stays open for hours, and what the automatic reconnect sends back.

  6. SSE vs WebSockets →

    The design-round comparison, easier once you know what SSE gives for free and what it costs.

  • Reaching for a WebSocket when the client only listens; the upstream actions usually fit as plain requests next to a one-way stream.

  • Assuming the client retries forever: some responses, such as a non-200 status or the wrong media type, stop reconnection permanently.

  • Emitting ids that the server cannot replay from, so Last-Event-ID arrives on reconnect and means nothing.

  • Trusting a stream that works locally; a proxy, compressor or framework buffer in production can hold events back and release them in bursts.

  • Forgetting that Cache-Control: no-cache controls caching, not buffering — see Cache-Control: no-cache on a stream.

  • Leaving an idle stream silent, so an intermediary's idle timeout cuts it and departed viewers go unnoticed until the next write.

  • Treating the opening authorization as permanent when the stream can outlive the credential that admitted it.

SSE is one of four common ways to get server updates to a client, and interviewers expect you to place it among them. **Long polling** repeats a request the server holds until it has news; it works almost anywhere but pays a request per update. **WebSockets** upgrade the connection into a full-duplex message channel, which suits chat, collaborative editing and games where the client talks as often as it listens, at the cost of leaving plain HTTP semantics behind. **Streaming fetch** reads any response body incrementally; it gives full control over method and headers but none of the reconnect behaviour SSE defines. SSE sits between them: one-way, text, HTTP-native, with resumption built in. It is the usual transport for streaming model output from LLM APIs, for dashboards, notifications and progress feeds, and it is one of the transports GraphQL subscriptions can run over, alongside WebSockets. Over HTTP/2 and HTTP/3 many streams share one connection, which sidesteps the small per-origin connection limit browsers apply over HTTP/1.1.

report an issue with this guide →

questions

page 2 of 2

Can a Server-Sent Events stream be delivered in an encoding other than UTF-8, and how would a client know?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

No. An event stream is always encoded and decoded as UTF-8, and the grammar offers no way to announce or select another encoding. A client never inspects anything to decide - it decodes as UTF-8 unconditionally.

open as a page

In a Server-Sent Events response, why must a receiver never treat a `chunked` transfer chunk boundary as an event boundary?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

A transfer coding is hop-by-hop framing with no application meaning, and any intermediary may re-chunk the body as it forwards it. One chunk can hold two and a half events, or half of one, so chunk edges say nothing about where an event ends.

open as a page

Should one Server-Sent Events response stay open for a fourteen-hour firing, and how would you bound its lifetime?

level: seniorimportance: nice to knowfreq 32%

basics

~20 s

Usually not. Cap a response's lifetime at minutes to an hour and end it deliberately by completing the body; a conforming client re-opens by itself. An unbounded response pins a connection slot, its subscription, and the viewer to one instance.

open as a page

showing 31–33 of 33