skip to content

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%

answer

  1. right bytes, wrong schedule
  2. something is collecting the body
  3. one buffered layer is enough
  4. flush after the blank line
  5. flush granularity is delivery granularity

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.

solid answer

~40 s

The bytes are correct and the timing is wrong, which always points at accumulation rather than at the grammar. Somewhere between the code that renders an event and the socket there is a layer holding bytes until it has enough of them, or until the response completes: a buffered writer, a response-wrapping layer that builds the body to measure or transform it, or a template-style layer that only emits when the handler returns. One such layer is enough — the stream can be byte-perfect and still look dead. The fix has two halves: **flush after the blank line that terminates each event**, and make sure no wrapper above the writer is collecting the body at all. Flushing into an accumulating wrapper changes nothing.

code

pseudocode · 8 lines
pseudocode
function stream_firing(firing, out):
    out.send_headers(200, "text/event-stream")
    for each change in firing.stage_changes():
        out.write_line("event: stage")
        out.write_line("data: " + json(change))
        out.write_line("")      // the blank line terminates the event
        out.flush()              // without this the bytes wait in a buffer
    out.end()                    // completes the response body

go deeper

for a junior

Remember the rule rather than the diagnosis: each event is written, then flushed, so it leaves immediately instead of waiting for company.

for a middle

Explain the mechanics — where bytes accumulate above the writer, why one such layer defeats every flush below it, and why the flush belongs after the event's terminating blank line.

for a senior

Demonstrate the diagnosis: correct content with wrong timing means accumulation, and you walk the write path from the source outward instead of guessing at the grammar.

for a principal

The tradeoff worth owning is that streaming responses need a deliberately thinner path than document responses, and some conveniences must be excluded from it by policy.

## The symptom names the fault Every event eventually arrives, in order, correctly formed, and all at once. That combination rules out the grammar: a malformed stream loses or mangles events, it does not delay them evenly. What it points at is a layer that is collecting bytes instead of passing them on. The kiln monitor makes it vivid. A fourteen-hour firing emits a stage change every few minutes; the watcher sees a blank panel all day and then two hundred events land together at 11pm. Nothing errored. Nothing is missing. The stream was simply never live. ## Where the accumulation hides on the emitting side - **A buffered writer** wrapping the response output, whose whole job is to avoid small writes — exactly the behaviour a live stream must not have. - **A response-wrapping layer** that captures the body to measure it, hash it, transform it, or apply a content coding, and therefore cannot release anything until the body is complete. - **A serialization layer** that expects to be handed a finished value and writes it in one go when the handler returns. - **The application's own batching** — an event queue drained on a timer that happens to be far longer than the feed's tempo. The important property is that these compose badly: **a single accumulating layer anywhere in the write path is enough to hide every event until it releases**, no matter how diligently the layers below it flush. That is why the diagnosis is a walk down the path rather than a single check. ## Where the flush belongs 1. **Render the event's lines.** 2. **Write the blank line that terminates the event.** A conforming client dispatches on that blank line and not before. 3. **Flush.** Now the receiver has a complete event and can act on it. 4. Go back to waiting on the source. Flushing in the middle of an event is not wrong, but it buys nothing: the receiver holds the partial lines until the terminator arrives, so the event still surfaces at step 2's write. Flushing once per batch of events, or once at the end, is the bug being discussed. Flush granularity is delivery granularity. ## Telling an origin-side stall from a downstream one | observation | what it points at | |---|---| | a raw client connected straight to the origin sees events one at a time | the origin is flushing; the accumulation is somewhere downstream | | the same raw client also sees one clump at the end | the origin itself is accumulating — this question's case | | events arrive in fixed-size clumps rather than at the end | a buffer is filling and releasing, wherever it lives | | the first event is instant and later ones clump | a layer that engaged after the response began, such as a content transformation | The downstream half of that table — what an intermediary does to a long-lived response, and the header field that opts a response out of it — is a separate subject with its own mechanics. This one is the origin's own duty: produce bytes that are already live by the time they leave the process. ## What flushing does not fix - **It does not make a silent stream credible.** Between kiln stages there is nothing to flush; keeping an idle response alive is a different duty. - **It does not bound the response's lifetime.** A perfectly flushed response can still be held open longer than is wise. - **It does not tell you the receiver is still there.** It is, however, what makes that discovery possible — detection rides the write path, and a write that never happens fails nothing. - **It does not compensate for a source that batches.** If the feed hands you twenty stage changes at once because it polls every twenty minutes, flushing delivers twenty events at once, correctly and uselessly. ## What an interviewer is listening for Three things, in order. First, that you reach for **delivery timing**, not for the event grammar, when the content is right and the schedule is wrong. Second, that you can name **more than one place** bytes can pile up, because candidates who have only met one keep flushing at a layer that is not the culprit. Third, that you know **flushing into an accumulating wrapper is a no-op** — the fix is to remove the wrapper from the path for this response, not to flush harder underneath it.

  • Where exactly in the sequence does the flush belong, and why there?
    After the blank line that terminates the event. A conforming client dispatches an event only when it reads that blank line, so flushing earlier pushes bytes the receiver must hold anyway. Flushing later — per batch, or at the end — is what turns a live feed into a delayed one.
  • How would you tell an origin-side stall apart from something downstream?
    Connect a raw client directly to the origin and read the response as it arrives. If events appear one at a time there, the origin is flushing correctly and the accumulation is downstream — an intermediary's behaviour, which is a different subject with its own opt-outs. If the same clump appears, the origin is the culprit.
  • The handler flushes after every event and the clumping persists. What now?
    Walk the write path outward. A wrapper above the writer that captures the body to measure or transform it will absorb every flush underneath it, so the flush is a no-op. Remove that wrapper from this response's path rather than flushing more often beneath it.

saying these in an interview costs you the question

  • Says the events were lost, when they were only late.
  • Assumes every write reaches the network the moment it is called.
  • Flushes once when the whole run is finished.
  • Blames the receiving application for batching events it never received.
  • Reaches for the event grammar when the bytes are already correct.
  • Keeps flushing beneath a wrapper that is collecting the body.