Autocomplete is the canonical machine-coding round at frontend interviews. The candidate has 45-60 minutes to build a working autocomplete component from scratch — usually with debouncing, keyboard navigation, and basic accessibility. The exercise tests whether the candidate can ship a working interactive component under time pressure, and the rubric covers far more than “does it work.”
This piece walks through the round end-to-end. What the prompt looks like, the implementation patterns that score well, the common pitfalls, and how to prepare.
The typical prompt
Common forms:
- “Build an autocomplete component. The user types into an input; suggestions appear below; the user can pick a suggestion via mouse or keyboard.”
- “Build a typeahead search using this mock API endpoint. Debounce requests so we don’t overwhelm the API.”
- “Implement an Algolia-like instant search component.”
The interviewer often provides a mock data source (an array of strings) or a function that returns a Promise. The candidate provides the React component (or vanilla JS, depending on the role).
What interviewers grade
- Working code. The component must function — the user can type, see suggestions, and pick one.
- Debouncing. Typing should not fire a request on every keystroke. 200-300ms debounce is typical.
- Keyboard navigation. Up/down arrow keys to navigate suggestions, Enter to select, Escape to close.
- Accessibility. ARIA roles, focus management, screen reader announcements.
- Edge cases. Empty input, no results, in-flight request when input changes (race condition handling).
- Code organization. Components separated cleanly, custom hooks where appropriate.
- Communication. Narrating decisions and tradeoffs out loud.
The implementation
Step 1: the basic structure
function Autocomplete({ fetchSuggestions, onSelect }) {
const [query, setQuery] = useState('');
const [suggestions, setSuggestions] = useState([]);
const [activeIndex, setActiveIndex] = useState(-1);
const [isOpen, setIsOpen] = useState(false);
return (
<div className="autocomplete">
<input
type="text"
value={query}
onChange={e => setQuery(e.target.value)}
onFocus={() => setIsOpen(true)}
/>
{isOpen && suggestions.length > 0 && (
<ul>
{suggestions.map((s, i) => (
<li key={s.id} className={i === activeIndex ? 'active' : ''} onClick={() => onSelect(s)}>{s.label}</li>
))}
</ul>
)}
</div>
);
}
Step 2: the debounced fetch
// Custom hook
function useDebouncedValue(value, delay) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(id);
}, [value, delay]);
return debounced;
}
// In the component
const debouncedQuery = useDebouncedValue(query, 250);
useEffect(() => {
if (!debouncedQuery) {
setSuggestions([]);
return;
}
let cancelled = false;
fetchSuggestions(debouncedQuery).then(results => {
if (!cancelled) setSuggestions(results);
});
return () => { cancelled = true; };
}, [debouncedQuery]);
The cancellation flag prevents stale results from overwriting fresher ones if a slower request resolves after a newer one. This is the most-missed edge case at the senior level.
Step 3: keyboard navigation
function handleKeyDown(e) {
if (e.key === 'ArrowDown') {
e.preventDefault();
setActiveIndex(i => Math.min(i + 1, suggestions.length - 1));
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setActiveIndex(i => Math.max(i - 1, 0));
} else if (e.key === 'Enter') {
e.preventDefault();
if (activeIndex >= 0) {
onSelect(suggestions[activeIndex]);
setIsOpen(false);
}
} else if (e.key === 'Escape') {
setIsOpen(false);
}
}
// On the input
<input ... onKeyDown={handleKeyDown} />
Arrow keys must call preventDefault or the browser will move the cursor in the input.
Step 4: accessibility
<input
type="text"
role="combobox"
aria-expanded={isOpen}
aria-controls="autocomplete-listbox"
aria-activedescendant={activeIndex >= 0 ? `option-${activeIndex}` : undefined}
...
/>
<ul id="autocomplete-listbox" role="listbox">
{suggestions.map((s, i) => (
<li
key={s.id}
id={`option-${i}`}
role="option"
aria-selected={i === activeIndex}
...
>{s.label}</li>
))}
</ul>
The combobox / listbox / option pattern is the WAI-ARIA standard for autocomplete. Screen readers will announce the active option as the user navigates.
Common pitfalls
- No debounce. Firing a request on every keystroke. The debounce is part of the spec, not optional.
- Race condition. A slow request resolving after a newer query has been typed, replacing fresh results with stale ones. Cancellation flag is the fix.
- No keyboard nav. Mouse-only autocomplete fails accessibility and modern frontend interview standards.
- preventDefault missing on arrow keys. Without it, the cursor moves in the input box during navigation.
- Active index out of sync. When suggestions change, the active index might point to nothing. Reset to -1 on new results.
- Click-outside not handled. The dropdown should close when the user clicks outside the autocomplete.
- No loading state. Strong candidates show a spinner or “loading…” while a request is in flight.
- No empty state. When the query has results but there are none to show, an “no results” message is helpful.
Stretch goals
If time allows, strong candidates add:
- Highlight the matched substring within each suggestion.
- Cache previous results to avoid re-fetching identical queries.
- Limit suggestions to a max count.
- Add a “no results” message.
- Handle the case where an in-flight request fails (error state).
- Memoize the suggestion list to avoid unnecessary re-renders.
How to communicate during the round
Strong candidates narrate decisions:
- “I’ll use a custom debounce hook so the value passed to the API is debounced.”
- “The race condition matters here — let me add a cancellation flag.”
- “For keyboard navigation, I need preventDefault on arrow keys, otherwise the cursor moves in the input.”
- “For accessibility, the combobox/listbox/option pattern is the WAI-ARIA standard. Let me add the relevant ARIA attributes.”
Silent coding — even when the code is correct — scores worse than narrated coding because the interviewer cannot see the reasoning.
Time budget for a 45-minute round
- 0-5 min: clarify requirements, ask about debounce delay, keyboard support, accessibility expectations.
- 5-15 min: basic structure and rendering.
- 15-25 min: debounced fetch with race condition handling.
- 25-35 min: keyboard navigation.
- 35-40 min: accessibility (ARIA attributes, focus management).
- 40-45 min: edge cases, error handling, stretch goals if time.
How to practice
- Build the autocomplete from scratch three or four times. Each time without looking at notes.
- Time yourself. The 45-minute budget is real; practice fitting the work into it.
- Practice narrating out loud as you code. Record yourself; listen back.
- Build variants: search-as-you-type with multiple categories, autocomplete with multi-select, autocomplete with caching.
- Read the WAI-ARIA combobox spec until the ARIA attributes are reflexive.
Frequently Asked Questions
Should I use a UI library?
Generally no. The round tests whether you can build the component from scratch. Some interviewers permit using libraries for non-essential parts (CSS framework, utility hooks); confirm at the start.
Is the autocomplete round common at FAANG?
Yes for frontend roles. Less common at generalist SWE roles. The exact problem (autocomplete vs infinite scroll vs drag-drop) varies by company.
What if the interviewer asks for something I haven’t built before?
Talk through the design first. Ask clarifying questions. Even if the implementation is rough, demonstrating clear architectural thinking scores higher than blindly typing.
Is keyboard navigation always required?
It is part of the WAI-ARIA standard for combobox patterns and is usually expected at senior+ levels. At junior levels, candidates can sometimes get away with only mouse interaction, but it is graded.
How is this different from a standard React tutorial?
Tutorials skip the production concerns: race conditions, accessibility, keyboard navigation, edge cases. The interview round specifically tests these. Practice the production version, not the tutorial version.