Design Autocomplete / Typeahead Search
System design for building a Google-like autocomplete search component: debouncing, caching, keyboard navigation, race conditions, and mobile considerations.
Designing an autocomplete or typeahead search component is a common frontend system design question. It exercises your understanding of async patterns, performance optimization, and accessibility. Here's how to approach it in an interview.
Requirements Clarification
Functional Requirements
- As the user types, show a dropdown of matching suggestions.
- User can select a suggestion with mouse/tap or keyboard (Enter, arrow keys).
- Selecting a suggestion either navigates or populates the input.
- Clear the suggestions when the input is blurred (or after selection).
- Optional: show recent searches, trending topics, or category groupings.
Non-Functional Requirements
- Latency: Suggestions should appear within 100–300ms of user input.
- Debouncing: Avoid firing a request on every keystroke—typically 200–400ms delay.
- Caching: Cache results per query to avoid redundant network calls.
- Race conditions: Only show results for the most recent request; ignore stale responses.
- Accessibility: Full keyboard navigation, screen reader support, ARIA attributes.
- Mobile: Touch-friendly, consider reduced motion, virtual keyboard behavior.
High-Level Architecture
┌─────────────────────────────────────────────────────────┐
│ AutocompleteContainer │
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ SearchInput │ │ SuggestionsDropdown │ │
│ │ (debounced) │─►│ (virtualized if large list) │ │
│ └─────────────────┘ └─────────────────────────────┘ │
│ │ ▲ │
│ ▼ │ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ useAutocomplete hook │ │
│ │ - query state, suggestions, loading, selectedIdx │ │
│ │ - fetchSuggestions (with abort), cache (Map) │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Data flows: user types → debounce → fetch (or cache hit) → update suggestions → user selects or blurs.
Component Design
SearchInput
Wraps a native <input>. Handles onChange (debounced), onKeyDown (ArrowUp/Down, Enter, Escape), and onBlur (with a short delay so clicks on suggestions register). Exposes a ref for focus management.
SuggestionsDropdown
Renders a list of suggestions. Each item highlights the matched portion of the query (e.g., "google"). Uses role="listbox" and role="option". Tracks hover and keyboard-selected index for visual highlight.
useAutocomplete Hook
Centralizes logic: debounced query, fetch function, cache (Map with query → suggestions), loading state, selected index, and logic to handle race conditions via AbortController or request IDs.
interface UseAutocompleteOptions {
fetchSuggestions: (query: string) => Promise<Suggestion[]>;
debounceMs?: number;
minChars?: number;
}
interface UseAutocompleteResult {
query: string;
setQuery: (q: string) => void;
suggestions: Suggestion[];
isLoading: boolean;
selectedIndex: number;
setSelectedIndex: (n: number) => void;
selectSuggestion: (s: Suggestion) => void;
}
State Management
- query: Local state (or controlled from parent). Drives fetch and dropdown visibility.
- suggestions: Server state. Stored in hook state; optionally persisted in cache (Map).
- selectedIndex: UI state. Reset when suggestions change; clamped between 0 and suggestions.length - 1.
- loading: Derived from in-flight request.
- cache: In-memory Map. Key = normalized query (trimmed, lowercased); value = suggestions + timestamp. Optional TTL for eviction.
API Design
Typically a single endpoint:
GET /api/suggestions?q=goo&limit=10
Response: { suggestions: [{ id, text, type?, metadata? }] }
Keep responses small. Use pagination or a limit cap (e.g., 10). Consider returning highlighted fragments or let the client do client-side highlighting for flexibility.
Performance Considerations
- Debouncing: 200–400ms prevents request spam. Use
useDebouncedValueorlodash.debounce. Cancel previous timeout on new input. - Caching: In-memory Map keyed by query. Reduces API calls for repeated or backspaced queries.
- Race conditions: Use
AbortController—abort the previous request when a new one fires. Or use a request ID: only apply results if the response’s ID matches the latest request. - Virtualization: If suggestions can be 100+ items, use
react-windowor@tanstack/react-virtualfor the dropdown. - Highlighting: Prefer CSS or simple string replacement over heavy regex. Consider
dangerouslySetInnerHTMLonly when sanitized.
Accessibility
- Keyboard: ArrowUp/Down to move selection; Enter to select; Escape to close.
- ARIA:
aria-expanded,aria-activedescendanton input;role="listbox"androle="option"on list;aria-selectedon active option. - Live region: Use
aria-live="polite"to announce count when results load. - Focus: On open, move focus to input or first suggestion based on product requirements. On close, return focus to input.
- Screen readers: Ensure the relationship between input and list is clear (
aria-controls,aria-owns).
Trade-offs and Extensions
Trade-offs: Aggressive debouncing improves performance but can feel sluggish. Shorter debounce (100ms) feels snappier but increases server load. Cache size vs. memory—consider LRU eviction.
Extensions: Add analytics (impressions, selections). Support recent searches (localStorage). Add categories or structured results (people, places). Implement server-side highlighting vs. client-side. Add prefetch for popular queries. Consider GraphQL or federated search for multiple backends.
Edge Cases and Robustness
Handle empty and error states: show "No results" when the API returns an empty list, and a retry or message when the request fails. If the user clears the input, clear suggestions and reset selected index. When the dropdown is open and the user clicks outside, close it and return focus to the input. If the API is slow, consider a stale-while-revalidate pattern: show cached results immediately and update when the latest response arrives (still respecting race-condition handling). Test with slow 3G and rapid typing to ensure the UI stays consistent and never shows results for an outdated query.
Related articles
- System DesignDesign a Search Results Page
System design for a search results page: filters, sorting, facets, infinite scroll vs pagination, and performance at scale.
Read article - System DesignDesign a Comments Thread
System design for nested comments: replies, pagination, real-time updates, and moderation. Frontend architecture and data shape.
Read article - System DesignDesign a Social Media News Feed
System design for a Twitter/Facebook-style news feed: infinite scroll, virtualization, optimistic updates, real-time updates, lazy loading, and content ranking.
Read article - System DesignDesign a Real-Time Chat Application
System design for Slack/WhatsApp-style chat: WebSocket management, message ordering, offline support, typing indicators, read receipts, search, and file uploads.
Read article