Topic 15.1
Interview Questions: Streaming and State
In one line
The questions asked about streaming and state in an AI frontend interview, with answers that give the mechanism, an example, the edge case and the trade-off. Three live examples show the byte layer, a stop button and a race.
Think of it like this
A driving test. The examiner does not only ask whether you can drive on an empty road. They ask what you do when a child runs out, when the brakes feel soft, when the road is wet. An interview about AI frontends is the same: the happy path is assumed, and the questions go to the places where it breaks.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Token
- The unit a model reads and writes: a word, part of a word or a punctuation mark. Not the same as a chunk.
- SSE (server-sent events)
- A text format for a stream: lines like
data: {json}with a blank line between events. - Chunk
- One piece of a stream as the network delivers it. Its edges have nothing to do with words, lines or characters.
- Line buffer
- A string you keep between reads to hold the unfinished last line of a stream until the rest arrives.
- AbortController
- A browser object whose
signalyou pass tofetch. Callingabort()cancels the request. - AbortError
- The error a cancelled
fetchor read rejects with. It means "you asked me to stop", not "something broke". - Race
- Two things happening at once where the result depends on which finishes first.
- Stale response
- An answer to an old question that arrives after a newer question was asked.
- Request id
- A value you attach to each request so you can tell later which request an answer belongs to.
- Reducer
- A pure function
(state, event) => newState. All chat changes go through one. - Single source of truth
- One place that holds the state, so two parts of the screen cannot disagree.
Step by step
01Answer in five moves
Practise this order with every question in this phase: answer, mechanism, example, failure, trade-off. One sentence for the answer. Two or three sentences for the mechanism. One concrete example (ideally from something you built). The failure case. The cost of your choice.
The model answers in this topic follow it. Cover the answer, say yours aloud, then compare. Notice what you left out, usually the failure case.
02From the network to the pixels
Most streaming questions are about one stage of the road a token travels. Know the road. Each stage has its own bug, and the interviewer is usually pointing at one of them.
03Live example 1: bytes, characters and lines
The first example fetches a stream cut into 8-byte packets, which a real network can also do. It reads the same packets with two parsers. The careless one decodes every packet alone and treats each packet as whole lines. The careful one decodes with { stream: true } and keeps the unfinished last line in a buffer.
Read the last two result lines. The careless parser loses every word (it prints an empty text) and meets broken characters. The careful parser returns the full text. This is the whole answer to "what can go wrong when you read an SSE stream by hand?" (Topics 2.3 to 2.5).
04Live example 2: stop is an abort, not an error
The second example reads a stream, calls controller.abort() after the third piece, and reports what the code sees: an AbortError. It also prints that the server saw the abort (the handler listens on req.signal), so it can stop paying for tokens. Then it breaks a stream on purpose and shows that a real failure has a different error name. The rule: an AbortError you caused is silent, every other error is shown (Topic 3.1).
05Live example 3: the late answer
The third example sends a slow question and then a fast one. The slow answer arrives last. A reducer that applies every answer shows the old answer in place of the new one. A reducer that checks the request id ignores it. Interviewers love this question because the broken code looks fine in a demo (Topic 4.7).
06State and rendering questions
The last group of questions has no single bug, so it is answered with a design: one reducer, stable message ids as React keys (never the array index), only the streaming message re-rendering as tokens arrive, and scroll that follows the bottom until the user scrolls away. Topics 4.1, 4.2, 2.6 and 2.7 give you the detail. Topic 15.2 uses the same ideas in a design round.
Try it yourself
- 1
Make the packets smaller
In the first example change
splitBytes: 8to1and press Run. The careful parser still returns the full text, because it only cares about newlines, not packet edges.Then change it to
500. Both parsers now work, because every line arrives whole. That is why a bug like the careless parser passes every test on a fast local network and fails for a user on a slow connection. - 2
Stop at a different piece
In the second example change
read("ok", 3)toread("ok", 1). Stopping after the first piece works the same way. Change it toread("ok", 100). The answer ends before piece 100, so the stop never happens and the read finishes normally (8 pieces). The example then waits for a server abort that never comes, so nothing more prints. That is the point: the server only sees an abort when the browser sends one. - 3
Release the gate early
In the third example move
release();to just beforeawait ask(2, "fast"). Now the slow question answers first, the fast one second, and both reducers print the same. A race only exists when the order is unlucky, which is why it is hard to find by testing.
Code & diagrams
The model is a scripted stand-in. In a real app the only change is the server: it calls your provider and passes the stream through. The browser code does not change.
Preview
Console
Nothing logged yet.Expected console output
the coffee cup is 3 bytes: 226 152 149
decoding only the first 2 bytes: "�"
decoding both halves with stream: true: "☕"
packets received: 111
careless: { text: '', skippedLines: 6, brokenCharacters: 3 }
careful: { text: 'Hi ☕ Mitra' }Check the error name, not the message. Messages differ between browsers; the name AbortError is the same everywhere.
Preview
Console
Nothing logged yet.Expected console output
Stop pressed: after 3 pieces the read threw AbortError: our own Stop, show nothing
the server saw the abort and stopped
Connection broke: after 2 pieces the read threw TypeError: a real failure, tell the user
No Stop, no break: finished after 8 piecesThe gate is how the example makes the slow answer arrive last on every run, without a timer. A real network does it by itself, on a bad day.
Preview
Console
Nothing logged yet.Expected console output
naive: after the fast answer: Answer to the fast question.
naive: after the late answer: Answer to the slow question.
careful: after the fast answer: Answer to the fast question.
careful: after the late answer: Answer to the fast question.Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Decode without stream: true
In the first example, in careful, change decoder.decode(bytes, { stream: true }) to decoder.decode(bytes) and press Run.
Myth vs fact
Myth
A chunk is a token.
Fact
A chunk is whatever the network delivered. It can hold several tokens, half a token, or half a character. Parse the format, not the chunk edges (Topic 2.2).
Myth
Stopping a request is the same as hiding the answer.
Fact
Hiding it leaves the request running and paying for tokens. Aborting closes the connection, and a good server stops the model when it sees the abort (Topic 3.1).
Myth
A finally block is enough to avoid stale answers.
Fact
A finally block cleans up. Ignoring stale answers needs an id check or an abort of the old request (Topic 4.7).
Myth
EventSource is the right tool for chat.
Fact
EventSource only does GET and cannot send a JSON body or headers. Chat uses fetch and reads the same event format (Topic 2.4).
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Aborting and checking an id solve different problems. Aborting stops the old request from costing anything. The id check protects the screen if an answer still slips through (a cached response, a request already past the point of no return). Use both for anything that spends money.
- ▸
When you describe a bug in an interview, say how you would find it: a test with a stream cut into small packets, a test where the second request finishes first, a log line with the request id. Interviewers want to hear that you can prove it.
- ▸
Learn the names, not only the code:
TextDecoder,ReadableStream,AbortSignal,AbortError,finish_reason. A precise word shows that you have used the thing.
Remember this
- 1
The problem this solves: You know the material from Phases 0 to 14, but an interview asks you to say it out loud, in a few minutes, to a stranger. Many people know the code and cannot explain why it is written that way. This topic gives you the questions that come up again and again about streaming and state, and a way to answer each one so that it sounds like experience and not like a memorised list.
- 2
A shape for every answer. Say the answer in one sentence first. Then the mechanism (what really happens). Then a small example. Then the edge case or failure ("and if the connection drops halfway…"). Then the trade-off ("the cost of this choice is…"). Interviewers stop you when they have heard enough, so the one-sentence answer must come first, and the depth must be behind it.
- 3
What streaming questions test. Whether you know that a stream is bytes and not words; that a chunk is cut by the network and not by meaning; that a multi-byte character or a line can be split between two chunks; and that
fetchdoes not throw for a429. The live example below shows what goes wrong when you assume otherwise. - 4
What stop and race questions test. Whether you know that stopping is an
AbortController, that the abort shows up as anAbortErrorwhich is not a failure to show the user, that the server can see the abort, and that an old request can still answer after a new one started. A request id (or an abort of the old request) is the fix. Both examples below show the broken version first. - 5
What state questions test. Whether your chat state is one source of truth with stable ids, whether impossible states (loading and finished at once) can exist, and whether you can say what is stored where: on the screen, in the browser, on the server. Rendering questions come last: appending tokens without re-rendering the whole history, and not fighting the user over scrolling.
- 6
Say what you do not know. "I have not used that library, but I would check how it handles a half-finished Markdown bold" is a better answer than a confident guess. This course teaches the reasons, so when a tool changes you can still reason about it.
Explain it without notes
Why stream an AI answer at all, and when would you not?
How do you read a streamed fetch response, and what are the traps?
A user on a slow connection sees garbled text with strange characters in the middle of an emoji. What happened and how do you fix it?
How does an SSE stream look on the wire, and why parse it yourself with fetch instead of using EventSource?
How does a Stop button work, and how do you tell a stop from a real failure?
Users sometimes see the answer to their previous question. What is the bug and how do you fix it?
How would you structure the state of a chat so that bugs like that cannot happen?
What do you do so that a long streaming answer does not make the page janky or fight the user's scrolling?
Practice
Write a function makeLineReader(onLine) that returns feed(bytes) and end(). It must handle a line split across packets and a character split across packets. Test it on the packets ["da", "ta: a", "b\n\ndata: c", "\n\n"].
A teammate's Stop button hides the answer but the server keeps running. List two things you check in their code and the one-line fix for each.
Trade-offs
- ↔
Writing the stream parser yourself teaches the format and gives full control, but it is code you must test with split packets. An SDK parser is tested for you, and you give up some control over edge cases.
- ↔
Ignoring stale answers with an id keeps the screen right but still lets the old request run to the end. Aborting the old request saves money but needs care so the abort is not shown as an error. Real apps do both.
Done when you can
I can answer a question in five moves: answer, mechanism, example, failure, trade-off.
I can explain why a chunk is not a token and show a split character and a split line.
I can explain the Stop button,
AbortError, and what the server sees.I can describe the stale-answer race and two ways to prevent it.
I can say how the chat state is structured so impossible states cannot exist.
I can name the topic in this course that teaches each answer.