Loading...
Loading...
Senior · 45 min
Also asked as: Google Search suggestions, Algolia InstantSearch, Slack quick switcher, GitHub command palette, Amazon product search bar.
Read it once, then start the timer and work without scrolling.
Design a search autocomplete component that suggests results as the user types, fast enough to feel instant and cheap enough to run on every keystroke.
Everything below is a model answer. You will get far more out of this drill by attempting it cold first — set the timer, talk through it out loud, and only then compare.
A 45-minute phase plan you can practise against a clock.
Interactive · Drill rehearsal timer
6:00
Time remaining in Clarify and scope.
What you should establish before drawing a single box.
Architecture, data, interfaces, and optimisations — the RADIO body.
The half of the answer most candidates skip entirely.
Naming how the system breaks, and what the user sees when it does, is the cheapest way to signal production experience. Work through these before the interviewer has to ask.
The interviewer will challenge you. Rehearse the concession, not just the defence.
Interviewer
Debouncing just makes the product feel slow. Why not query on every keystroke and let the server handle it?
Model response
You are right that debounce is literally added latency, and if the backend were free and instant I would remove it. The reason I keep it is that the alternative is not faster for the user — it is a burst of eight requests for an eight-character query, all but the last of which are wasted, and on a congested mobile connection those queued requests delay the one that matters. So debounce is not really about server cost, it is about head-of-line blocking on the client. The mitigation that gets me most of both worlds is the cache: if the query resolves from the LRU I skip the debounce entirely and render synchronously. If we had measured p50 backend latency under 30ms and mostly desktop traffic, querying every keystroke would be defensible and I would test it.
Interviewer
This is a search box. Why are you spending time on ARIA instead of on the ranking and the caching?
Model response
Fair, and ranking is genuinely higher-leverage for search quality — I explicitly descoped it because it is usually a backend concern. But the combobox is one of the few widgets where getting accessibility wrong actively breaks sighted keyboard users too, not just screen reader users. If focus moves into the listbox instead of staying in the input, arrow keys stop editing text and the whole interaction model breaks for everyone. So the ARIA work here is not a compliance checkbox bolted on at the end, it is the same work as getting the keyboard model right. It is maybe fifteen lines of attributes once the state machine is correct, so the cost is low and the cost of retrofitting it is high.
Interviewer
Your client-side cache will serve stale suggestions. If we launch a new product, people typing its name will not see it.
Model response
That is true and it is the specific cost I am accepting. The reason it is acceptable here and would not be for, say, an inventory count, is that a suggestion is a navigational hint rather than a fact — the user is going to press Enter and hit the live search anyway, so a stale suggestion costs one extra keystroke, not a wrong decision. I bound the exposure three ways: the in-memory LRU dies with the tab, the HTTP layer uses a 60-second max-age with stale-while-revalidate so the stale window is short and self-healing, and I would add a cache-version header so a deploy can invalidate everything at once for a launch. If the requirement were that new items must appear within seconds, I would drop the HTTP cache and keep only the session LRU.
Interviewer
Why not just ship the whole corpus to the client and skip the network entirely? Fuse.js over a JSON file would be simpler.
Model response
For a bounded corpus that is genuinely the better answer and I would take it — a command palette over a few thousand commands should never touch the network. The line for me is roughly where the payload exceeds a couple hundred kilobytes gzipped, because that cost is paid by every user on first load whether or not they ever open search, and it competes directly with the LCP budget. The second constraint is freshness: a shipped corpus is as stale as the bundle, so anything that changes hourly is out. And personalized or permission-filtered results cannot ship to the client at all, because you would be handing the user a list of documents they are not allowed to see. So: small, public, slow-changing corpus goes client-side, and I would keep the matcher interface pluggable so we can move that line later without rewriting the component.
Specific signals an interviewer is listening for on this prompt.
Turn one attempt into retained skill.