Question presented to candidate: "Build a search box for our book catalog. It should not hit the server on every keystroke: wait until the user pauses for about 300 ms, then search. If an older request is still running when a newer one starts, cancel it so stale results can never show up. Show a loading state, an empty state and an error state."
Requirements:
- One text input with a label and a clear (ร) button.
- Fire the search 300 ms after the last keystroke (debounce), never once per key.
- Cancel the previous in-flight request when a new search starts, and cancel everything on unmount (no late state updates).
- Show "waiting", "searching", results, "no results" and error states; keep the previous results visible (dimmed) while the next request runs.
- The server can be simulated with a Promise and
setTimeout.
What a strong answer should cover:
- Debounce as "reset a timer on every change, fire when it survives", implemented as a reusable hook / composable / RxJS operator, with the timer cleared on unmount.
- Separating the raw input value (updates every keystroke) from the debounced term (drives the request).
AbortControllerpassed to the request, aborted in the effect cleanup, and anAbortErrortreated as "not an error".- Why debounce alone does not prevent stale results: requests can still finish out of order.
- Loading / empty / error states and an accessible status message (
role="status"). - How to test it: fake timers, or driving real time in small steps.
Clarifying questions expected:
- "How long should the debounce be, and should an empty box clear the results immediately?"
- "Does the API accept an abort signal, or do I need to ignore stale responses myself?"
- "Should results update while typing (search-as-you-type) or only on submit?"
Code / implementation expected: Yes. A working component with a debounce helper, an aborted fetch per new term and the loading / empty / error states.