🇮🇳
🇮🇳
Republic Day Special Offer!Get 20% OFF on all courses
Enroll Now
P
Prakalpana
📚Learn
Code Your Future
System Design⏱️ 16 min read📅 Dec 28

UI System Design: Build Autocomplete/Typeahead - Complete Guide

AV
Ankit VermaSenior Frontend at Google
📑 Contents (11 sections)

📌Problem Statement

Design an autocomplete/typeahead component like Google Search or Amazon search bar.

📌Requirements

Functional

  • Show suggestions as user types
  • Keyboard navigation (up/down/enter)
  • Mouse click selection
  • Recent searches
  • Highlight matching text
  • Non-Functional

  • Fast response (< 100ms perceived)
  • Handle rapid typing (debounce)
  • Accessible (ARIA)
  • Mobile friendly
  • 📌Component Architecture

    const Autocomplete = () => {
    const [query, setQuery] = useState('');
    const [suggestions, setSuggestions] = useState([]);
    const [isLoading, setIsLoading] = useState(false);
    const [selectedIndex, setSelectedIndex] = useState(-1);
    const [isOpen, setIsOpen] = useState(false);
    return (
    <div className="autocomplete-container">
    <input
    type="text"
    value={query}
    onChange={handleInputChange}
    onKeyDown={handleKeyDown}
    onFocus={() => setIsOpen(true)}
    role="combobox"
    aria-expanded={isOpen}
    aria-autocomplete="list"
    />
    {isOpen && suggestions.length > 0 && (
    <ul role="listbox">
    {suggestions.map((item, index) => (
    <li
    key={item.id}
    role="option"
    aria-selected={index === selectedIndex}
    onClick={() => handleSelect(item)}
    >
    {highlightMatch(item.text, query)}
    </li>
    ))}
    </ul>
    )}
    </div>
    );
    };

    📌Debouncing Implementation

    Prevent API calls on every keystroke:

    const useDebounce = (value, delay) => {
    const [debouncedValue, setDebouncedValue] = useState(value);
    useEffect(() => {
    const timer = setTimeout(() => {
    setDebouncedValue(value);
    }, delay);
    return () => clearTimeout(timer);
    }, [value, delay]);
    return debouncedValue;
    };
    const Autocomplete = () => {
    const [query, setQuery] = useState('');
    const debouncedQuery = useDebounce(query, 300);
    useEffect(() => {
    if (debouncedQuery.length >= 2) {
    fetchSuggestions(debouncedQuery);
    }
    }, [debouncedQuery]);
    };

    📌Caching Strategy

    Cache previous results to avoid redundant API calls:

    const cache = new Map();
    const CACHE_TTL = 5 * 60 * 1000;
    const fetchWithCache = async (query) => {
    const cacheKey = query.toLowerCase();
    if (cache.has(cacheKey)) {
    const { data, timestamp } = cache.get(cacheKey);
    if (Date.now() - timestamp < CACHE_TTL) {
    return data;
    }
    }
    const response = await fetch(`/api/search?q=${query}`);
    const data = await response.json();
    cache.set(cacheKey, { data, timestamp: Date.now() });
    if (cache.size > 100) {
    const oldestKey = cache.keys().next().value;
    cache.delete(oldestKey);
    }
    return data;
    };

    📌Keyboard Navigation

    const handleKeyDown = (e) => {
    switch (e.key) {
    case 'ArrowDown':
    e.preventDefault();
    setSelectedIndex(prev =>
    prev < suggestions.length - 1 ? prev + 1 : prev
    );
    break;
    case 'ArrowUp':
    e.preventDefault();
    setSelectedIndex(prev => prev > 0 ? prev - 1 : -1);
    break;
    case 'Enter':
    if (selectedIndex >= 0) {
    handleSelect(suggestions[selectedIndex]);
    }
    break;
    case 'Escape':
    setIsOpen(false);
    setSelectedIndex(-1);
    break;
    }
    };

    📌Highlight Matching Text

    const highlightMatch = (text, query) => {
    const regex = new RegExp(`(${query})`, 'gi');
    const parts = text.split(regex);
    return parts.map((part, i) =>
    regex.test(part)
    ? <mark key={i} className="highlight">{part}</mark>
    : part
    );
    };

    📌Performance Optimizations

  • 1Debounce: 300ms delay before API call
  • 2Cache: Store recent results
  • 3Cancel previous requests: Use AbortController
  • 4Virtual scrolling: For large suggestion lists
  • 5Lazy loading: Load suggestions in batches
  • 📌Accessibility (a11y)

  • role="combobox" on input
  • role="listbox" on suggestions
  • aria-selected for current selection
  • aria-expanded for dropdown state
  • Keyboard navigation support
  • Asked at Google, Amazon, and Microsoft frontend interviews.

    AV

    Written by

    Ankit Verma

    Senior Frontend at Google

    🚀 Master System Design

    Join 500+ developers

    Explore Courses →
    Chat on WhatsApp