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 1 of 2

In a Server-Sent Events stream, what does a `retry:` field sent by the server tell the client to do?

level: juniorimportance: must knowfreq 58%

answer

  1. the only reconnect lever the server has
  2. changes when, not whether
  3. a default applies before the first one
  4. survives into later reconnects
  5. milliseconds, not seconds

basics

~10 s

A retry field sets, in milliseconds, how long a client waits before re-opening a dropped Server-Sent Events stream. Until the server sends one, the client uses an implementation-defined default of a few seconds.

solid answer

~40 s

`retry:` carries a reconnection delay in **milliseconds**, and it is the only say the emitting side has over the client's automatic re-open. A Server-Sent Events client re-opens a dropped response by itself, with no application code in the path; before any `retry:` arrives it waits an implementation-defined default, commonly a couple of seconds. Once the server writes `retry: 15000`, that client waits fifteen seconds instead, and the value stays in effect for later reconnects on the same stream until another `retry:` replaces it. It changes *when* the client comes back, not *whether* it comes back. Send it early, ahead of the first event, so a client that drops in the first second already has your number rather than its own.

code

http · 9 lines
http
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache

retry: 15000

id: 20480
event: level
data: {"gauge":"upper-weir","metres":3.41}

go deeper

for a junior

Remember two facts: a Server-Sent Events client re-opens a dropped stream on its own, and the retry field is the server telling it how many milliseconds to wait first.

for a middle

Be able to explain that the value persists for the stream rather than for one event, that a default applies until the first one arrives, and that it sets timing only.

for a senior

Show that you pick the number from the cost of re-establishing the feed against how stale the surface may go, and that you raise it deliberately when shedding load.

for a principal

The interesting angle is what a single fleet-wide delay cannot express: identical timing for every client, so spreading return traffic has to be solved somewhere other than this field.

## What a `retry:` line actually sets A Server-Sent Events feed is one HTTP response that the server never finishes. The client issues an ordinary `GET` with `Accept: text/event-stream`, the server answers `200 OK` with `Content-Type: text/event-stream`, and then writes events into the body for as long as the feed lasts. Because it is an ordinary response, it can end at any moment: a laptop lid closes, a radio link out to a river gauge drops in exactly the weather that made the feed worth watching, an intermediary decides a quiet response has been idle long enough. The client does not surface that as a failure and wait for your code to react. It re-opens the request by itself. `retry:` is how the server gets a vote on **when**. - The value is a delay in **milliseconds**. `retry: 15000` means fifteen seconds — not fifteen milliseconds, and not fifteen attempts. - It is a property of the stream as a whole, not of the event it happens to sit near, so it does not have to be repeated on every event. - It affects *when* a client returns, not *whether* it returns: the separate question of which responses tell a client to stop reconnecting altogether is not decided by this field. - The direction is fixed. The **server** writes `retry:`; the **client** obeys it. There is no client-to-server field of this name. ## Before the server says anything A client that has just opened a feed has no delay from you yet. The specification leaves the starting value to the implementation, and in practice it is a small number of seconds. That has two consequences worth thinking about for a district warning console: | situation | delay in effect | |---|---| | Stream opened, no `retry:` seen yet | the client's own implementation-defined default, typically a few seconds | | `retry: 15000` received | fifteen seconds, from then on | | `retry: 15000`, then later `retry: 2000` | two seconds — the most recent value wins | | Response drops and is re-opened | whatever value was last in effect carries over | First, if you never send the field you have accepted whatever the client chose, on every client type that connects to you. Second, because the value survives reconnects, sending it once near the top of the response is enough; you do not have to re-state it after every gauge reading. ## Choosing a number for a real feed The delay trades freshness against load, and both sides of that trade are yours to reason about: 1. **Cheap feed, freshness matters.** A short delay — one or two seconds — means a console that flickers offline for a moment is current again almost immediately. This is the usual choice for a warning surface where a stale threshold crossing is the whole risk. 2. **Expensive feed.** If re-opening means re-authorising, re-reading a snapshot and re-subscribing to a dozen gauges, a one-second delay turns a brief network wobble into repeated expensive work. Lengthen it. 3. **You are shedding load.** Raising the advertised delay before you start closing responses spaces out the return traffic. Note the limit of the mechanism: it is a single number, identical for every client that reads it, so it cannot by itself stop a fleet from coming back in lockstep. Spreading reconnects out per client is a technique that lives above this field, not inside it. ## The three things candidates get wrong The unit is the first: milliseconds, and a value written as if it were seconds is a thousandfold error in the wrong direction. The direction is the second: this is a response field, and a client never sends it. The third is a cross-protocol collision — in an RPC framework a "retry" is a policy for re-issuing a failed call, possibly several times, possibly with backoff. Here it is not a policy and not a count. It is one delay, before one re-open of one stream, of a client that was going to re-open anyway.

  • A stream sends `retry: 20000` at the top and `retry: 2000` an hour later. Which delay is in effect after that?
    Two seconds. The most recent value replaces the previous one for that client's stream, and it keeps applying to subsequent re-opens until another `retry:` field arrives. There is no accumulation and no averaging — the field simply assigns the current reconnection delay.
  • Why send `retry:` before any event rather than alongside the first one?
    Because a response can die in its first second, and a client that never read a `retry:` falls back to its own default. Writing the field as the first thing in the body means even the shortest-lived connection leaves the client holding the delay you chose.

saying these in an interview costs you the question

  • Reads the value as seconds, so the delay is a thousand times off.
  • Thinks a client will not reconnect at all unless the server sends retry.
  • Believes the client sends retry to the server.
  • Treats it as a count of reconnection attempts before giving up.
  • Assumes it must be repeated on every event to stay in effect.
  • Confuses it with an RPC framework's policy for re-issuing a failed call.
open as a page

How does a handler that emits a Server-Sent Events stream differ from one that returns an ordinary JSON response?

level: juniorimportance: must knowfreq 60%

basics

~20 s

An event-stream handler never finishes its response. It answers with 200 OK and Content-Type: text/event-stream, declares no body length, writes each event as that event happens, flushes, and holds the response open instead of returning a document.

open as a page

What does Server-Sent Events define for you that a WebSocket connection leaves you to build, and what can it never do?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Server-Sent Events defines automatic reconnection and a text event grammar over one ordinary HTTP response; a WebSocket connection defines neither, so you build them. The stream is server-to-client only, so anything the client sends needs a separate request.

open as a page

In a Server-Sent Events feed of elevator car positions, what media type does the response carry, and what ends one event?

level: juniorimportance: must knowfreq 74%

basics

~20 s

The response is served as text/event-stream. Its body is a stream of field lines - data, event, id and retry - and a single blank line ends the current block and dispatches it as one event.

open as a page

In a Server-Sent Events stream, when is the response authorized, and what re-checks that authorization while it stays open for hours?

level: middleimportance: must knowfreq 66%

basics

~20 s

Authorization happens once, when the server answers the opening GET with 200 OK and a text/event-stream body. Nothing in the protocol re-checks it afterwards; the body is only data lines, so a fresh decision needs a fresh request.

open as a page

A Server-Sent Events feed streams smoothly in local tests but arrives in clumps through a reverse proxy — why?

level: middleimportance: must knowfreq 68%

basics

~20 s

An intermediary is accumulating the response body and releasing it only when its buffer fills. HTTP defines what a message means, not how promptly a proxy must forward part of one, so a response written event by event arrives in blocks.

open as a page

After a Server-Sent Events response drops, how does the client tell the server which events it already received?

level: middleimportance: must knowfreq 68%

basics

~20 s

The server tags events with an id field; a client remembers the most recent value and, on reconnect, repeats the same GET with a Last-Event-ID request header carrying it, so the server can replay what was missed before resuming live.

open as a page

Your Server-Sent Events endpoint delivers a whole firing's events in one clump at the end, with no proxy involved — why?

level: middleimportance: must knowfreq 64%

basics

~20 s

Something above the writer is accumulating the body. An event stream is only live if every event leaves when it is written, so the emitting side must flush after the blank line that ends each event, with no layer in between collecting bytes.

open as a page

Your Server-Sent Events board is right for updates, but patrol tablets must occasionally send a trail closure. What do you do?

level: middleimportance: must knowfreq 56%

basics

~20 s

Keep the one-way stream for updates and send each upstream message as an ordinary HTTP request to the same service. That pairing holds while upstream traffic is occasional; at continuous rates or tight per-message latency, a duplex socket wins.

open as a page

A fault report spans three lines in a Server-Sent Events stream - how does the client rebuild that one payload?

level: middleimportance: must knowfreq 60%

basics

~20 s

Each data line contributes its value. The client joins them with a line feed and removes the final one when the block dispatches, so three data lines become one three-line payload. A single space after the colon is stripped.

open as a page

An investigator's Server-Sent Events feed has streamed for six hours and the credential that authorized it expired two hours ago — what are your options?

level: seniorimportance: must knowfreq 58%

basics

~20 s

Three, and no more: keep emitting to the end of the response, end the response so the client's own reopen is authorized afresh, or emit a terminal application-level event first and then end it. The already-sent status cannot be revised.

open as a page

During a rolling restart your arrivals feed answered 503 Service Unavailable for two minutes, and afterwards no client ever came back - why?

level: seniorimportance: must knowfreq 58%

basics

~20 s

Every client received an HTTP response whose status was not 200 OK, so each one failed the connection: no further attempt, ever. Only a network-level failure or an ended stream makes a client reestablish the connection.

open as a page

Your Server-Sent Events response is cut every sixty seconds whenever the feed goes quiet, though the origin never ended it — what cuts it, and what does the receiving client do?

level: seniorimportance: must knowfreq 58%

basics

~20 s

An intermediary's idle timeout ends it. That clock measures the gap since the last byte it forwarded, so a live but silent response looks dead. Because the 200 OK already went out, the body simply stops, and the receiving client reads that as a dropped stream and reconnects.

open as a page

A Server-Sent Events endpoint answers 200 OK but with Content-Type: application/json - what does a conforming client do?

level: juniorimportance: should knowfreq 45%

basics

~10 s

A conforming client fails the connection permanently. It accepts only 200 OK carrying Content-Type: text/event-stream; anything else means no events are dispatched and no reconnection is ever attempted, so the feed simply stays empty.

open as a page

What does `Cache-Control: no-cache` on a Server-Sent Events response actually stop, and what does it not?

level: juniorimportance: should knowfreq 48%

basics

~20 s

Cache-Control: no-cache stops any cache on the path from reusing a stored copy of the response without revalidating with the origin first. It does not forbid storing the body, and it has no effect at all on an intermediary that buffers the stream.

open as a page

Your text/event-stream feed emits a line with no colon and a field name the grammar never defines - how does a conforming client treat each?

level: middleimportance: should knowfreq 42%

basics

~20 s

Both are tolerated, not rejected. A non-empty line with no colon is processed as a field name with an empty value, and a field name the grammar does not define is ignored outright. Neither ends the stream.

open as a page

How can `Content-Encoding: gzip` on a Server-Sent Events response defeat incremental delivery, and which directive pushes back?

level: middleimportance: should knowfreq 42%

basics

~20 s

A compressor that accumulates input until it has a block worth of bytes emits nothing in the meantime, so events disappear into it and surface in groups. Cache-Control: no-transform is the standard directive telling an intermediary not to change the body's content coding.

open as a page

Nothing is emitted on a Server-Sent Events stream for twenty minutes and clients quietly vanish — what does an idle stream need?

level: middleimportance: should knowfreq 50%

basics

~20 s

A periodic comment line: bytes a conforming client ignores and that dispatch no event. The event-stream specification's authoring notes suggest roughly every fifteen seconds, because a quiet connection gets dropped and a silent emitter never notices.

open as a page

In a Server-Sent Events feed, what does adding an `event: fault` line change about the block it appears in?

level: middleimportance: should knowfreq 52%

basics

~20 s

It sets the type of the event that block dispatches, to the literal name fault. It changes nothing else: the payload, the parsing and the blank-line dispatch are identical, and the name applies to that one block only.

open as a page

What makes the `id:` you attach to each Server-Sent Events event actually usable for resuming a stream?

level: seniorimportance: should knowfreq 45%

basics

~20 s

An id is resumable only if it addresses the store the server replays from: assigned by the server, ordered so that everything-after-it is computable, stable across instances and restarts, and short enough to ride back in a request header on every reconnect.

open as a page

A console reconnects to your Server-Sent Events feed with a `Last-Event-ID` older than anything the server still holds — what should it do?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Treat it as a signalled gap rather than a fresh subscriber: emit a distinctly named event marking the break plus a current snapshot, then continue live. Silently streaming from now on hides missing readings behind a healthy-looking stream.

open as a page

Your Server-Sent Events emitter is still producing events for viewers who closed the page hours ago — how does it find out?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Usually only when a write fails. A one-way response carries no message from the receiver, so on a stream that is silent for long stretches the departure surfaces at the next write — which is one more reason to write a periodic comment.

open as a page

How does the per-connection cost of a held Server-Sent Events response compare with a WebSocket connection at 200,000 viewers?

level: seniorimportance: should knowfreq 42%

basics

~20 s

At the connection level they are nearly the same: one socket, TLS state and send buffers per viewer either way. The real differences are per-message overhead, the encoding tax on binary payloads, and how each side re-establishes after a drop.

open as a page

How does choosing Server-Sent Events instead of a WebSocket connection change what intermediaries and your authorization layer see?

level: seniorimportance: should knowfreq 48%

basics

~20 s

An event stream stays an ordinary request and response, so every intermediary and authorization rule in the path understands it — and may also transform or cut it. An upgraded connection is opaque after one admission decision, for good and ill.

open as a page

Your Server-Sent Events emitter writes a final fault block and closes the response, but clients never see that event - why?

level: seniorimportance: should knowfreq 42%

basics

~20 s

The block was never terminated by a blank line, so the client is still accumulating it. An incomplete block sitting in the parser's buffers when the body ends is discarded, not delivered - the end of the response dispatches nothing.

open as a page

Across a fleet of long-lived Server-Sent Events feeds, how would you set a maximum response lifetime to fix how often streams re-authorize?

level: principalimportance: should knowfreq 38%

basics

~20 s

Set it as a staleness budget: an authorization decision lasts exactly as long as its response, so the maximum lifetime you allow is the maximum staleness you are promising. Tier it by how sensitive the feed is, and price the reopens.

open as a page

Would you standardise your organisation on Server-Sent Events for live feeds rather than WebSockets per team, and what does that commit you to?

level: principalimportance: should knowfreq 33%

basics

~10 s

Usually yes, with a written exception trigger. One default transport means one authorization story, one capacity model and one runbook; the commitment is owning that exception process and the products it genuinely fails.

open as a page

In a Server-Sent Events stream a server sends retry: 5s and an id value containing U+0000 NULL - what happens to each?

level: middleimportance: nice to knowfreq 28%

basics

~20 s

Both fields are ignored. A retry value is only accepted when it is entirely ASCII digits, and an id value containing U+0000 NULL is discarded, so each buffer silently keeps the value it already held.

open as a page

In a Server-Sent Events stream, what is the client's last event ID after an event that carries no `id:` field?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

The client's last event ID is unchanged: it is set only when an event carries an id field, and it persists through every later event that omits one, so a feed can label every hundredth event and still resume from that checkpoint.

open as a page

showing 1–30 of 33