Build a Streaming Chat Input
OpenAIFrontend Engineer · Technical ScreenLast updated
This question evaluates frontend engineering skills including streaming data handling, incremental token-by-token rendering, asynchronous state management, and preserving UI responsiveness during long-running API streams.
Implement a minimal front-end chat interface, similar to a stripped-down AI assistant (think a bare-bones ChatGPT). The user types a prompt, submits it, and the assistant's reply streams in token-by-token below the input rather than appearing all at once.
You are given an API endpoint that returns the assistant response as a stream of text chunks (the exact transport — fetch + ReadableStream, Server-Sent Events, or WebSocket — is up to you to assume and state). Your job is to build the component: its structure, its state management, and its incremental-render behavior.
Core Requirements
- Render a text input and a submit button.
- On submit, call the streaming API with the user's prompt.
- Render the streamed response below the input as chunks arrive, not after the full response completes.
- Keep the UI responsive while the stream is in progress (input is not frozen; the page does not jank).
- Prevent or gracefully handle duplicate submissions while a response is still streaming.
- Show basic loading and error states.
Constraints & Assumptions
- Single-page component; no router, no global state library required (though you should be able to justify whether you'd reach for one).
- The API contract is provided by the interviewer. Assume a single endpoint (e.g.
POST /api/chatwith a{ prompt }body and anAbortSignal) that returns the reply as a stream of text chunks and eventually completes or errors. The exact transport is yours to choose — state the contract shape you assume, and whether you wrap it in a helper (streamChat(prompt, { signal })) or consume the stream directly. Either is fine as long as it's explicit. - A response may consist of dozens to hundreds of small chunks arriving over a few seconds.
- Target a modern evergreen browser; you may use the framework you're most fluent in (React is the common default for this role), but be ready to defend vanilla-JS or framework-specific choices.
- No need for persistence, auth, markdown rendering, or multi-turn history unless you choose to add them — focus the core on the streaming render loop.
Clarifying Questions to Ask
- What exactly is the streaming transport and chunk format — raw
fetchbody stream, SSE (text/event-stream), or WebSocket? Are chunks plain text or JSON-wrapped events with a terminal sentinel? - Should the user be able to cancel/stop an in-progress response, or only wait for it to finish?
- Is this single-turn (one prompt → one reply) or do you want a scrolling multi-turn transcript?
- What should happen to a half-streamed answer when an error occurs mid-stream — keep the partial text, discard it, or offer a retry?
- Any accessibility requirements (screen-reader announcement of streaming text, focus management)?
- Should typed input be preserved or cleared on submit, and should Enter submit?
Hints
Hint 1
Where to start: List the *distinct visible conditions* the UI can be in across the whole lifecycle (nothing yet, waiting, mid-stream, finished, failed). Decide how you'll represent "which condition am I in" before you touch the network — a clean representation will make each requirement (disable button, spinner, partial text, error) fall out of it. Mutually-exclusive booleans tend to drift into impossible combinations; is there a tighter shape?
Hint 2
Consuming the stream: A streaming `fetch` exposes the body as a reader you pull from in a loop until it's done. The bytes arrive as raw `Uint8Array`s, not strings. Think about what happens at a *chunk boundary*: a single emoji or CJK character is several bytes — what if those bytes land in two different reads? Convince yourself your byte-to-text conversion is safe across that boundary.
Hint 3
Render performance & responsiveness: If you push state on *every* arriving chunk, how many renders does a few-hundred-chunk response cost — and does that scale with response length? Is there a way to decouple "how fast chunks arrive" from "how often the UI repaints"? Separately, ask why the input doesn't freeze: what kind of work is the read loop actually doing on the main thread?
Hint 4
Duplicate submissions & cancellation: A "submit" while one response is in flight has two failure modes: a *second* request firing, and the *first* one never being stopped. What's the minimum that blocks the new submit at the UI level, and what handle would you need to keep around to actively stop the work already running (on a new submit, a Stop button, or unmount)? Make sure a deliberate stop doesn't look like a failure.
Hint 5
Cleanup & races: Two things can outlive the request that started them: a read loop after the component is gone, and a slow chunk from an old request arriving after a newer one began. Both end with "update some UI that no longer belongs to me." Is there a single piece of bookkeeping you can check after each `await` to tell whether *this* iteration still owns the current view?
Follow-up questions
- How would you extend this to a **multi-turn conversation** with a scrolling transcript while keeping the streaming bubble performant?
- The product wants a **Stop generating** button. Walk through the implementation and what state has to change.
- A response can be thousands of tokens. How do you keep scroll position sensible (auto-scroll-to-bottom vs. "user scrolled up, don't yank them down")?
- How would you make the streaming text **accessible** to screen-reader users, and how would you test the streaming behavior deterministically (mocking the chunk stream)?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.