AI Application Integration · 2 to 3 hours
Implement a streaming state reducer
Implement a pure reducer that keeps a streamed answer consistent through stale runs, replays, gaps, and stops, and predict its transitions. Then review a fetch integration for decoding, framing, cancellation, and end-of-stream defects.
3 h
1
Step 1: The question
Implement reduceStream to the contract and predict the transition fixtures. Then find the defects in the fetch integration and describe a corrected flow with per-run AbortControllers. Explain how your design keeps stale-run events, duplicate deltas, and a stream that ends without a terminal event from corrupting the UI, and how the UI announces and labels each terminal state.
Define an event contract
Each server event carries a run ID and a one-based sequence number. A delta event appends text, done marks a complete answer, and error ends the run with a safe error message. The sequence covers every persisted event for that run, so a client can tell a replay from a gap.
The client starts a run through a local action. It accepts only events that belong to the active run and requires contiguous sequence numbers. An event that was already applied is ignored, and a gap marks the response interrupted rather than silently losing text.
The duplicate rule assumes the payload for a sequence number never changes; two different payloads under one number are a protocol violation for server-side integrity checks to catch.
Local Stop means the client stopped this attempt. It does not assert that the server cancelled every operation. A separate reconciliation step can later update the persisted run record from authoritative server state.
The reducer contract
The reducer receives the current state and one action and returns the next state. It is pure: no clock, no network, and no mutation of its inputs. It never throws: a bad sequence number becomes a failed state, not an exception. When an action changes nothing it returns the same state object, so useReducer can skip the re-render; any change returns a new object with exactly the five fields runId, status, text, lastSeq, and error.
type Status = "idle" | "streaming" | "complete" | "interrupted" | "stopped" | "failed";
type State = {
runId: string | null;
status: Status;
text: string;
lastSeq: number;
error: string | null;
};
// seq is untrusted network data: the reducer validates it before use (rule 4).
type ServerEvent = { runId: string; seq: unknown } & (
| { type: "delta"; text: string }
| { type: "done" }
| { type: "error"; message: string }
);
type Action =
| { type: "start"; runId: string }
| { type: "stop"; runId: string }
| { type: "disconnect"; runId: string }
| { type: "event"; event: ServerEvent };
const initialState: State = {
runId: null, status: "idle", text: "", lastSeq: 0, error: null
};
declare function reduceStream(state: State, action: Action): State;Apply these rules in order; the first one that matches decides the result.
startreturns a fresh streaming state for its run,{ runId, status: "streaming", text: "", lastSeq: 0, error: null }, whatever the current state.- Any other action whose run ID differs from
state.runId, or any other action whilestatusis not"streaming", returns the same state object. Complete, interrupted, stopped, and failed are therefore sticky until the nextstart. stopsets status"stopped", anddisconnectsets"interrupted". Text,lastSeq, and error are kept.- An event whose
seqis not a safe integer of at least 1 sets status"failed"with error"Invalid event sequence". - An event with
seq <= lastSeqwas already applied: return the same state object, not an equal copy. - An event with
seq !== lastSeq + 1sets status"interrupted"with error"Missing event", keeping text andlastSeq. - Otherwise apply the event and set
lastSeqto itsseq:deltaappends its text,donesets"complete", anderrorsets"failed"with the event's message as the error.
A gap ends live accumulation on purpose. Reconnecting needs a separate snapshot or resume operation (the Hard question in this topic), not more events sent into an interrupted state.
The Python version is reduce_stream(state, action) on dictionaries, with run_id and last_seq in place of runId and lastSeq. Everything else, including the status and error strings, is identical. In Python, seq must be an int that is not a bool, from 1 to 2**53 - 1.
Constraints
- The reducer is pure and synchronous: it returns the same object when nothing changes, a new object otherwise, and never mutates its inputs
- Apply the seven rules in the stated order, with the exact error strings "Invalid event sequence" and "Missing event"
- Events reach the reducer already decoded, framed, and validated; decoding, framing, and cancellation belong to the integration
Deliverables
- A reducer that passes the linked practice problem, and your predicted final state for every transition case
- How run identity and sequence numbers keep stale, duplicate, and out-of-order events from changing the answer
- The defects in the integration and a corrected flow: decoding, framing, validation, and the end of the stream
- Per-run cancellation for Stop, replacement, and unmount, and which handler may update which part of the UI
- Status announcements, labels for partial answers, and safe rendering for each terminal state
2
Step 2: Hints
Optional. Each hint gives a little more away.
3
Step 3: Work through it
Code exercise
Streaming state reducer
Implement reduceStream (Python reduce_stream) to the contract above. Apply the seven rules in order, return the same object when nothing changes, and never mutate the input state or action.
Expected
The linked practice problem writes its starting states as literals (it does not use your initialState), applies actions through your reducer, and compares results with toEqual (Python ==). Its six tests cover the nine transition scenarios from the guide as one table (T1 to T9 below, with the same starting states), applied delta, done, and error events, actions for another run or a finished run returning the same object, a replay (seq <= lastSeq) returning the same object rather than an equal copy (toBe in JavaScript, is in Python), gaps, invalid sequence values such as 0, 1.5, true, and 2**53, and a start that always resets while the inputs stay unmodified.
Limitations
The reducer trusts that a runtime validator already checked event types, field types, string sizes, and the run-ID format; TypeScript types alone do not validate network data. It checks sequence numbers defensively but is not a protocol parser: it does not decode bytes, abort requests, or reconcile with the server. It treats a gap as the end of live accumulation, so reconnecting needs a separate snapshot or resume operation, and its duplicate rule assumes the payload for a sequence number never changes.
Predict the outcome
Predict the transitions
Each case is an independent scenario: apply its actions, in order, to its starting state with the seven rules, and predict the final state before you reveal it. The starting states, written as { runId, status, text, lastSeq, error }, are idle = { null, "idle", "", 0, null }, r1Hello = { "R1", "streaming", "Hello", 1, null }, r1Stopped = { "R1", "stopped", "Hello", 1, null }, and r2Complete = { "R2", "complete", "Hi", 2, null }. Shorthand: start R1, stop R1, and disconnect R1 are actions for run R1. R1 delta 1 "Hello" is an event with run ID R1, seq 1, type delta, and text "Hello". R1 done 2 and R1 error 2 "Upstream timeout" are done and error events with seq 2.
| Case | Starting state | Actions, in order | runId | status | text | lastSeq | error | Last action returns the same object? |
|---|---|---|---|---|---|---|---|---|
| T1 | idle | start R1 | ? | ? | ? | ? | ? | ? |
| T2 | idle | start R1; R1 delta 1 "Hello" | ? | ? | ? | ? | ? | ? |
| T3 | r1Hello | R1 delta 1 "Hello" | ? | ? | ? | ? | ? | ? |
| T4 | r1Hello | R1 delta 3 " world" | ? | ? | ? | ? | ? | ? |
| T5 | r1Hello | stop R1 | ? | ? | ? | ? | ? | ? |
| T6 | r1Stopped | start R2 | ? | ? | ? | ? | ? | ? |
| T7 | r1Stopped | start R2; R2 delta 1 "Hi"; R1 delta 2 " world" | ? | ? | ? | ? | ? | ? |
| T8 | idle | start R2; R2 delta 1 "Hi"; R2 done 2 | ? | ? | ? | ? | ? | ? |
| T9 | r2Complete | disconnect R2 | ? | ? | ? | ? | ? | ? |
| T10 | idle | start R3; R3 delta 1 "Partial"; R3 error 2 "Upstream timeout" | ? | ? | ? | ? | ? | ? |
| T11 | idle | start R3; R3 delta 1 "Hi"; R3 delta 0 "x" | ? | ? | ? | ? | ? | ? |
| T12 | r1Hello | R1 delta 3 " world"; start R2 | ? | ? | ? | ? | ? | ? |
Diagnose
Review a fetch integration
Suppose a coding-practice platform streams hints. The backend accepts a request, assigns a run ID, and answers with status 200, content type application/x-ndjson, and an x-run-id header. It then streams one event per line in the event format above, and every frame, including the last, ends with a newline. The hint panel shows the reducer's text and status, plus a spinner driven by setLoading. A teammate connected the reducer like this (showBanner displays a page-level error):
let controller: AbortController | null = null;
async function requestHint(
prompt: string,
dispatch: (action: Action) => void,
setLoading: (on: boolean) => void
): Promise<void> {
controller = new AbortController();
setLoading(true);
try {
const response = await fetch("/api/hints", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ prompt }),
signal: controller.signal
});
const runId = response.headers.get("x-run-id") ?? "pending";
dispatch({ type: "start", runId });
const reader = response.body!.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = new TextDecoder().decode(value);
for (const line of chunk.split("\n")) {
if (line.trim() !== "") {
dispatch({ type: "event", event: JSON.parse(line) as ServerEvent });
}
}
}
} catch {
showBanner("Hints are unavailable right now.");
} finally {
setLoading(false);
}
}
function stopHint(): void {
controller?.abort();
}- Find at least five defects. For each one, give a concrete sequence of user actions or network behavior that triggers it, and what the learner sees.
- Describe the corrected flow from submit to the end of the stream: which controller each run uses, when
startis dispatched, how bytes become validated events, what happens at EOF, and what Stop, replacement, and unmount do. - Name one test, with a fake reader or a local fake server, that would catch each defect.
Kept in this browser. Saving your score below keeps a copy with it.
0 / 5000
4
Step 4: Reference answer
5
Step 5: Score yourself
Score each dimension 0, 1, or 2, then save the score to your history. Readiness thresholds are editorial estimates.
Grades your written answer against this rubric with your own AI provider key from Settings. It is a second opinion: the score you save is still yours.
0 of 10 · Scored 0 of 5 dimensions
Ready at 8 of 10, with no 0 in State transitions or Identity and sequence handling
Kept in this browser until you sign up, then it moves to your account.
Complete this level when the state tests pass and you can explain which guarantees still depend on the transport adapter and backend.
Your saved scores
Practice saves and mock interview answers on this question, newest first.
No saved scores yet. Score every dimension, then save.
6
Step 6: Follow-up questions
An interviewer may ask these next.
Why does each event carry both a run ID and a sequence number?
Why decode with a streaming decoder instead of decoding each read on its own?
What should the client do when the reader reaches EOF without a done or error event?
What can an outdated request's catch or finally handler break?
What should a status region announce while a response streams?
7
