cswork / libraryOpen practice workspace

Articles

Server-Sent Events Interview Questions: Reconnection, Event IDs, and Proxy Buffering

Prepare for SSE interviews with event framing, Last-Event-ID, reconnect recovery, replay and deduplication, proxy buffering, and five streaming exercises.

Your server logs one progress update every second. The browser displays twenty updates at once. After a brief disconnection, the next update appears twice. Both failures can happen in a Server-Sent Events system that returns HTTP 200 and uses the correct content type.

SSE interview questions are often easier to solve when you separate three contracts: how bytes become events, how a connection resumes, and how the application applies each event. Proxy buffering affects delivery timing. An event ID helps identify a resume position. Neither automatically makes an application update durable or unique.

Evidence boundary: Protocol and API facts below come from the HTML standard, Mozilla documentation, and NGINX documentation. The recovery timeline, debugging exercises, and design recommendations are original preparation material, not candidate reports or employer predictions.

What makes an SSE event complete?

SSE is a server-to-client event stream over HTTP. Mozilla’s EventSource reference describes the browser interface: create an EventSource for a URL and attach listeners. It is a useful fit for progress updates or notifications when client commands can use separate HTTP requests. Bidirectional interactive traffic may call for a different transport.

The HTML standard’s SSE section specifies UTF-8 text with the text/event-stream MIME type. Event boundaries are blank lines, not TCP packet boundaries. A minimal original named event is:

id: 101
event: progress
data: {"jobId":"j7","stage":"indexing"}

The final blank line matters. A client can receive part of a line, several events in one network read, or an event split across reads. “The server wrote once” does not imply “the browser dispatched once.”

Mozilla’s SSE usage guide documents field behavior: multiple data: lines join with newline characters; named events require listeners for their event type; comment lines start with a colon; retry: supplies a reconnection delay in milliseconds when its value is valid.

For an interview debugging exercise, send event: progress but register only onmessage. Then repair the listener. Next, omit the terminating blank line and inspect whether bytes arrive without a complete event being dispatched. These two failures look similar in a quiet UI but require different fixes.

Also distinguish native EventSource from a streamed fetch() response. Reading arbitrary response chunks does not give you EventSource’s framing and reconnection behavior. If you choose fetch for custom request headers or another requirement, explain who implements the parser, retry policy, and resume cursor.

What does automatic reconnection actually recover?

The HTML standard defines reconnection and transmission of a nonempty last event ID through Last-Event-ID. It also allows an HTTP 204 response to stop reconnection. Those are transport behaviors. The server must still decide what a supplied cursor means and whether the relevant history exists.

Use this original job-progress timeline:

StepBrowser or server actionRemaining correctness question
1Server emits event 101Did the client merely receive it, or apply it successfully?
2Connection dropsWhich events are already durable in the event log?
3Browser reconnects with cursor 101Does the server replay strictly after 101?
4Server sends 102, then accidentally repeats 102Can the application avoid applying the effect twice?
5Client reloads the whole pageWhere does its application resume position come from now?

A local native-browser fixture for this guide observed the reconnect request carrying Last-Event-ID: 101. It then delivered two events with ID 102 to the listener. A small application-level set suppressed the repeated application, leaving applied IDs 101 and 102. The browser did not deduplicate the events for us.

That experiment checks one browser and one localhost stream. It does not establish delivery guarantees for a production proxy chain or durable database consumer. Its useful lesson is narrower: automatic reconnect and application deduplication are separate mechanisms.

Define the resume boundary explicitly. In this example, cursor 101 means “return retained events strictly after 101.” If your server instead includes the boundary event, duplicates are expected even without an accidental resend. Either contract can be made understandable; ambiguity cannot.

Is Last-Event-ID a durable acknowledgment?

No application acknowledgment is created just because an event ID accompanies a received event. The browser’s protocol cursor does not prove your handler completed a database transaction, persisted a UI model, or finished an external side effect. This is an inference about application correctness from the protocol’s responsibilities, not an additional SSE guarantee.

Suppose event 102 instructs the client to update a local record. The event reaches the listener, but the local write fails. A reconnect based on the browser’s last received ID can move beyond work the application has not committed. If durable application processing matters, maintain an application cursor associated with successful processing and define a recovery endpoint that accepts it.

For a notification UI, a practical alternative is to treat events as invalidation hints. After reconnection, fetch authoritative current state and reconcile the view. For an append-only audit viewer, replay retained log entries using stable IDs. For a financial side effect, use an authenticated command and idempotency contract rather than making a browser notification the sole source of correctness.

A page reload creates a new EventSource instance. Do not assume it inherits a previous instance’s cursor. If the product needs resumption across reloads, store an appropriate application position or refetch a snapshot. Clarify which client identity, subscription, and stream the stored position belongs to.

An ID also need not be an integer. Our 101/102 example uses a single ordered numeric stream. Comparing arbitrary event IDs numerically or lexicographically would be an unjustified shortcut. A partitioned log may need a composite cursor, and authorization must be checked independently of the cursor’s contents.

How should a server handle missing history and replay races?

Start with a durable replay contract. Define event identity, ordering scope, retention, and the transition from historical replay to live delivery. These are original design recommendations; SSE does not prescribe a storage engine or retention period.

For one ordered stream, suppose events 101 through 150 exist when replay starts. Capture a high-water mark of 150, replay the requested range through that mark, and then continue from 151 using a mechanism that cannot lose events written during handoff. A subscriber attached too late can miss 151; one attached too early without a boundary can see it twice. Explain how your log reader or subscription mechanism closes that gap.

Now assume the client asks for 101 but retention begins at 130. Silently starting at 130 conceals a gap. Choose a product-specific reset path: supply a current snapshot with a new cursor, direct the client to a recovery endpoint, or display an explicit history-unavailable state. Do not rely on an arbitrary error status to produce the same retry behavior in every client implementation.

A snapshot needs its own consistency boundary. “Fetch the list, then start streaming” can miss changes made between those actions. Tie the snapshot to a position, or reconcile updates with versioned records and a documented overlap strategy. The right design depends on whether events represent complete state, incremental deltas, or hints to refetch.

For deduplication, scope the key to the relevant stream or entity. A bounded recent-ID set can be sufficient for transient display effects within a known replay window. A durable consumer may need a transactional record of the applied position. An unbounded in-memory set that grows for the life of the service is not a complete capacity plan.

Why do events arrive in bursts behind a proxy?

NGINX’s proxy module documentation explains response buffering and the X-Accel-Buffering response header. For an SSE route, inspect proxy_buffering; an upstream X-Accel-Buffering: no can affect it unless configuration ignores that header. This is response buffering, distinct from buffering a client’s request body.

A minimal route-specific configuration sketch is:

location /events {
    proxy_pass http://event_backend;
    proxy_buffering off;
    proxy_read_timeout 75s;
}

The timeout is an illustrative value, not a universal recommendation. The same NGINX reference explains that read timeout concerns the interval between successive upstream reads, rather than a fixed total lifetime for every response. Choose heartbeat timing against the actual intermediary timeouts you control.

To diagnose a burst, collect a small timing ledger:

MeasurementExample observationLayer to investigate next
Application event creationOne event each secondProducer cadence looks correct
Direct backend responseIncremental bytes each secondCompare proxied delivery
Proxied responseTwenty events released togetherProxy, compression, or gateway buffering
Browser dispatchImmediate after proxied bytes arriveUI render scheduling if display still lags

These are hypothetical observations that illustrate the decision process. They are not a measured NGINX benchmark. Compare the direct and proxied paths with an unbuffered client such as curl -N, while remembering that it does not test native EventSource parsing or browser UI scheduling.

Flush at the application layer, then inspect every relevant intermediary. A CDN, compression stage, or hosting platform may have its own behavior. A pending network request can be healthy for SSE; the important observations are incremental delivery, event dispatch, and bounded latency.

How do cleanup, authentication, and slow clients change the answer?

Mozilla’s EventSource API exposes close() to end a subscription. Call it when the owning view is destroyed or the subscription changes. Do not create another EventSource on every error callback while the original instance is still attempting its own reconnect; that can multiply subscriptions.

The native constructor accepts a URL and a credentials option, not an arbitrary request-header map. Choose authentication around that API and your origin/CORS arrangement. If requirements demand a custom authorization header, discuss another client implementation rather than pretending a second constructor argument can contain any headers you want.

For a long-lived subscription, define what happens when authorization expires or access is revoked. A valid cursor does not authorize access to the events at that position. Avoid placing durable secrets in query strings merely to make the constructor convenient.

On the server, an open connection should have a bounded outgoing queue. When a client cannot keep up, decide whether to coalesce state updates, drop replaceable hints, or disconnect and require replay. A comment heartbeat can keep a connection active without carrying a business update, but it does not solve a slow-consumer backlog.

In rehearsal, explain the full failure path aloud: disconnect, reconnect, authorization check, retained replay, duplicate handling, and cleanup. That gives the interviewer a concrete design to challenge rather than a promise that “SSE handles retries.”

Five SSE and streaming practice questions

These verified questions connect transport choice with frontend state and recovery. Their inclusion is a preparation recommendation, not a claim that an employer uses SSE in its current interview.

Practice questionWhat to rehearse
Choose Between REST, WebSocket, and Server-Sent Events for an Agent RuntimeJustify one-way progress plus separate command endpoints.
Build a Reliable Streaming Chat UIRoute updates by identity and reject duplicate or stale work.
Design a Notification UI with History and Live UpdatesConnect snapshot state, live updates, and reconnect recovery.
Design a live-stream comments systemExplain ordering scope and slow subscribers.
Design the Frontend of an AI Chat Application With Streaming RepliesSeparate active-stream bookkeeping from rendered state.

Begin with the protocol-choice question, then break the connection after one event. Use the Software Engineer collection to broaden the exercise once you can explain what each recovery mechanism guarantees.

Sources and further reading