Katkat

Implement an Accessible Autocomplete Search Component

AmazonFrontend Engineer · Technical Screen

Last updated

Design and implement an accessible, race-condition-resilient autocomplete search component in React or modern JavaScript, focusing on debounced queries, ARIA combobox semantics, and keyboard navigation.

Problem Description

Build an accessible, production-ready autocomplete (combobox) search component. The component accepts a user query, debounces the input, fetches matching suggestions from an asynchronous endpoint, and renders a listbox of results.

Because network latency varies, responses may arrive out of order (for example, a slower response for query "ap" might resolve after a faster response for "appl"). Your component must guarantee that suggestions displayed always reflect the current input query and never flash stale data.

Requirements

  1. Race Condition & Lifecycle Handling: Prevent stale responses from overwriting newer queries. Implement request cancellation (e.g., via AbortController) or an incremental request sequence counter.
  2. Debouncing: Delay triggering fetch calls by a configurable debounceMs (default 300ms).
  3. Accessibility (ARIA Combobox Pattern):
    • Input element must have role="combobox", aria-autocomplete="list", aria-expanded (true/false), and aria-controls pointing to the listbox ID.
    • Suggestions container must have role="listbox" and each item must have role="option".
    • Visual selection must be tracked via aria-activedescendant referencing the focused option's ID, keeping the actual DOM focus within the input.
  4. Keyboard Navigation:
    • ArrowDown / ArrowUp: Move visual focus through suggestions (wrapping or stopping at bounds).
    • Enter: Select currently highlighted suggestion and close dropdown.
    • Escape: Close suggestions dropdown without clearing input.
  5. UX & State:
    • Support loading and empty ("No results found") states.
    • Close suggestions when the user clicks outside or blurs the component.

Component Signature (TypeScript / React)

interface AutocompleteProps {
  fetchSuggestions: (query: string, signal?: AbortSignal) => Promise;
  renderOption: (item: T) => React.ReactNode;
  getOptionKey: (item: T) => string;
  onSelect: (item: T) => void;
  debounceMs?: number;
  placeholder?: string;
}

function Autocomplete(props: AutocompleteProps): JSX.Element;

Example Flow

  1. User types "b" at t = 0ms.
  2. User types "ba" at t = 100ms.
  3. The debounce timer expires at t = 400ms, sending a network request for "ba".
  4. If user presses ArrowDown, the first suggestion becomes visually active and aria-activedescendant is set to its ID.
  5. Pressing Enter executes onSelect with the active item and collapses the listbox.

Edge Cases

  • Empty input: Fetch should not trigger; close the suggestions list immediately.
  • Network failure: Catch rejected promises cleanly and display an accessible error state.
  • Unmounting: Cancel active requests and clear pending timeouts to prevent memory leaks.

Hints

Hint 1

Use AbortController inside a custom hook or an effect cleanup function to abort pending fetch requests whenever the query changes or unmounts.

Hint 2

For the ARIA combobox pattern, manage active item indices in state and dynamically assign aria-activedescendant to the focused option's DOM id rather than moving DOM focus out of the input.

Hint 3

Ensure cleanup of window event listeners and debounce timeout timers on every render cycle to avoid duplicate handlers and memory leaks.

Follow-up questions

  • How would you integrate client-side caching (e.g., LRU cache) to avoid refetching identical queries while ensuring cached results do not display stale data?
  • If the query returns thousands of items, how would you incorporate list virtualization while maintaining screen-reader announcements and keyboard navigation?
  • How would you test this component using React Testing Library to verify that out-of-order API responses are correctly ignored?