# Machine-Coding: Build an Autocomplete with Debouncing

Source: https://www.techinterview.org/post/3233474941/machine-coding-autocomplete-debouncing/
Updated: 2026-06-30 · techinterview.org

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.
