Katkat

Build a Debounced Autocomplete Search Bar

WaymoFrontend Engineer · Onsite

Last updated

This problem evaluates your ability to implement a robust, performant search component that handles asynchronous race conditions and user accessibility requirements.

Debounced Autocomplete Component

You are tasked with building an autocomplete search component. As a user types into an input field, the component should fetch suggestions from a remote API.

Requirements

  1. Debouncing: Ensure API requests are triggered only after the user has stopped typing for a specified delay (e.g., 300ms).
  2. Race Condition Handling: If a user types "ca", then "cat", and the "ca" request returns after the "cat" request, the UI must display only the results for "cat".
  3. Cancellation: If a new request is initiated, any pending or in-flight requests for previous, stale queries should be cancelled.
  4. Accessibility: The component must behave like a standard WAI-ARIA combobox, supporting keyboard navigation (Up/Down arrow keys) and Esc to close.
  5. Edge Cases: Handle rapid deletion (clearing the input), empty results, and network failures gracefully.

API Signature

Assume a mock function: fetchSuggestions(query: string, signal: AbortSignal): Promise.

Implementation

Provide a function or component structure that manages the state and effects. You may use React hooks or vanilla JavaScript with a class structure.

/**
 * @param {HTMLInputElement} inputElement
 * @param {HTMLElement} listContainer
 * @param {Function} fetchSuggestions
 */
function setupAutocomplete(inputElement, listContainer, fetchSuggestions) {
  // Implementation goes here
}

Example Flow

  • User types 'A' -> debounce starts.
  • User types 'B' -> first debounce timer cleared, new timer starts.
  • Timer expires -> fetchSuggestions('AB', signal) called.
  • User selects an item -> Input reflects selection, list closes.

Hints

Hint 1

Use a closure to hold the reference to the current AbortController so you can call .abort() on the previous request whenever a new one begins.

Hint 2

The 'input' event listener should trigger the debounced function, which subsequently manages the state update and API call logic.

Hint 3

Use 'aria-expanded', 'aria-controls', and 'role="listbox"' to ensure the component is accessible to screen readers.

Follow-up questions

  • How would you implement local caching (memoization) to avoid redundant network requests for identical queries?
  • How would you handle International Input Method Editors (IME), where multiple keystrokes compose a single character?
  • How do you ensure the component behaves correctly if the user quickly focuses and blurs the input field?