Eye Training · Streams and Streaming Data
Data arrives in pieces, at its own pace, and may stop half-way. Every streaming bug forgets one of those three.
Part 2 · Surface 15. Bug counts are hidden and some snippets are clean. See Start Here for how to use these drills.
The snippets use Node streams (pipe, pipeline, write), the web ReadableStream that fetch returns, server-sent events, and LLM token streams. The checks are the same for all of them.
J · The basics
Bug hunt
J · Read a webhook body
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
Bug hunt
J · Log the response
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
Bug hunt
J · Show the AI answer
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
Bug hunt
J · Compress a file
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
M · Three years in, these should jump out
Bug hunt
M · Download a file
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
Bug hunt
M · Export orders as CSV
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 2 min
Bug hunt
M · Write lines
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
Bug hunt
M · Stream text to the page
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 2 min
Bug hunt
M · Parse a server-sent event stream
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 2 min
Bug hunt
M · Live updates endpoint
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 2 min
Bug hunt
M · Chat endpoint
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 2 min
Bug hunt
M · Export all events
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 1 min
S · Where seniors stand out
Bug hunt
S · Line splitter
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 2 min
Bug hunt
S · Stream a JSON array
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 2 min
Bug hunt
S · Ask the assistant
Bug count hidden, and some snippets are clean. Click every line you think is wrong (or none), then check.
0:00 / 2 min
What your eye checks
Whenever data arrives in chunks, ask:
- Is the whole thing there yet? Data events,
read()and tokens arrive later; parse atendordone, not before. - Do chunk boundaries matter? Lines, JSON records and multi-byte characters can be split across chunks. Buffer the partial piece, and decode with
{ stream: true }or aStringDecoder. - Who handles errors?
pipedoesn't forward them;pipelinedoes. Every stream needs an error path. - Is there backpressure? If
write()returnsfalse, wait fordrain. Never load a whole table or file into memory to send it. - What happens when the client leaves? Listeners removed, upstream work (DB cursors, LLM calls) aborted, using
res.on('close'). - Is each piece a delta or the whole value? Append deltas; replace snapshots.
- What does the stream end with? A
[DONE]marker, a final partial line, a_flush. - What if it fails half-way? Headers and a 200 are already sent. Can the client tell the data is incomplete?
- Can two streams write into the same place? A shared "current" target gets interleaved tokens.
Tells
| You see | Ask |
|---|---|
req.on('data' and code right after it | Has the data arrived yet? |
res.text() and res.json() on one response | Can the body be read twice? |
x = chunk… inside a stream loop | Is the chunk a delta? |
.pipe( | Who handles errors? Who closes the source? |
res.write( in a loop | What does write return? |
decoder.decode(value) with no options | What about split characters? |
.split('\n') on a chunk | What about a split line? |
bus.on( inside a request handler | Removed on close? |
for await over an LLM stream in a route | What stops it when the client leaves? |
SELECT * with no LIMIT sent in one response | How many rows? |
A Transform with no _flush | What happens to the leftover? |
status(200) before a long loop | What if the loop fails? |
Go deeper
- Streams and Async Iterators: backpressure,
pipeline, and async iteration in depth - Cancellation and Cleanup: Part 9, cancelling LLM calls so you stop paying
- Previous surface: Events, listeners and sockets · Next: fetch and the network