Read real SSE-parsing and streaming-client snippets, predict the behaviour, and pick the highest-leverage fix — event framing, delta accumulation, client abort, and partial-JSON tool args.
AISenior◷ 14 min
Level
FoundationsJuniorMiddleSenior
The SSE parser, the accumulator, and the abort path are where streaming bugs actually live. Read each snippet, predict what it does under real traffic, then choose the fix a senior engineer makes first.
Goal
Practise the loop you run on every streaming incident: read the parsing and client code, predict where it corrupts or hangs, and reach for the framing/accumulation/cancellation fix rather than blaming the model.
Snippet 1 — the SSE frame parser
const reader = res.body.getReader();const decoder = new TextDecoder();while (true) { const { value, done } = await reader.read(); if (done) break; const text = decoder.decode(value); // one network chunk for (const line of text.split("\n")) { if (line.startsWith("data: ")) { const evt = JSON.parse(line.slice(6)); // parse immediately handle(evt); } }}
Quiz
Completed
This parser works in dev but throws JSON.parse errors under load. What is the bug, and the fix?
Heads-up Speed is not the issue — correctness is. The parse throws because the 'data:' payload is a fragment of a frame split across TCP chunks, not because JSON.parse is slow.
Heads-up fetch + ReadableStream is a valid and common way to consume SSE (and required for POST/auth headers). The defect is not buffering across chunk boundaries, which any correct parser must do.
Heads-up The server frames are valid; your reader is slicing them mid-frame at arbitrary TCP boundaries. Buffer until you have a full event before parsing.
Snippet 2 — the delta accumulator
let text = "";const toolArgs = {}; // index -> string bufferfunction handle(evt) { if (evt.type === "content_block_delta") { if (evt.delta.type === "text_delta") { text += evt.delta.text; render(text); } else if (evt.delta.type === "input_json_delta") { const i = evt.index; toolArgs[i] = JSON.parse(toolArgs[i] + evt.delta.partial_json); // (!) } }}
Quiz
Completed
The text path is correct. What is wrong with the tool-argument path?
Heads-up Each content block carries an index, and multiple tool calls in one message are distinguished exactly by that index. Indexing is correct; parsing per-delta is the bug.
Heads-up Tool args are structured input for a function, not display text. Even as a 'calling tool…' hint you show the raw partial string — you never act on it until the block closes.
Heads-up Wrapping the concatenation in JSON.parse means every intermediate fragment is parsed and throws. The canonical form accumulates a string and parses only at content_block_stop.
Snippet 3 — client cancellation
function ask(prompt) { return fetch("/api/chat", { method: "POST", body: JSON.stringify({ prompt }), }).then(consumeStream);}// User clicks "Stop". The UI stops rendering tokens.// But the server keeps generating, and billing keeps counting.
Quiz
Completed
Clicking Stop hides tokens but the model keeps generating and you keep paying. What is the correct fix end-to-end?
Heads-up Not rendering hides tokens locally but leaves the fetch, the server handler, and the upstream model generation all running. You keep paying for tokens no one sees.
Heads-up That caps every response, not just cancelled ones, and still cannot stop a generation early on user intent. Cancellation needs an abort signal propagated to the provider, not a length cap.
Heads-up This uses fetch/SSE, not WebSockets, and fetch streams are cancellable via AbortController. The real requirement is propagating the abort all the way to the upstream provider call.
Snippet 4 — server backpressure
// Express-style SSE endpoint proxying an upstream model streamfor await (const evt of upstreamStream) { res.write(`data: ${JSON.stringify(evt)}\n\n`); // ignore return value}res.end();
Quiz
Completed
A slow client (mobile, weak signal) consumes events slower than the upstream produces them. What does this loop do, and what is the fix?
Heads-up TCP flow-control fills the kernel buffer, but Node then queues unwritten data in userland memory unbounded. Ignoring write()'s false return is exactly how a slow consumer OOMs the server.
Heads-up write() never silently drops data; it buffers it in memory and signals fullness via the false return. The failure mode is unbounded memory growth, not lost events.
Heads-up Serialization cost is not the bottleneck for a slow client. The problem is producing faster than the client drains; the fix is honoring write() backpressure, not faster encoding.
Recap
Every streaming bug is read in the parser, the accumulator, the abort path, or the write loop: SSE frames must be buffered and split on the blank-line delimiter because TCP chunks don’t align with events; tool-arg input_json_delta fragments accumulate as a string and parse only at content_block_stop; client Stop must drive an AbortController whose signal propagates to the upstream provider or you keep generating and paying; and a server proxying a stream must honor write()‘s false return (pause upstream, resume on drain) or a slow client balloons memory. Read the code path, predict the failure under real traffic, fix the framing/cancellation/backpressure — then re-test against a slow, flaky client.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.