📌Problem Statement
Design an autocomplete/typeahead component like Google Search or Amazon search bar.
📌Requirements
Functional
Non-Functional
📌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
📌Accessibility (a11y)
role="combobox" on inputrole="listbox" on suggestionsaria-selected for current selectionaria-expanded for dropdown stateAsked at Google, Amazon, and Microsoft frontend interviews.