๐The Interview Setting
Company: Google Round: Frontend System Design Duration: 45 minutes Interviewer: Senior Staff Engineer
๐๐ค Interviewer: "Design the Google Search homepage and results page."
Candidate: "Before I start, let me clarify the requirements..."
Candidate asks:
Interviewer: "Focus on the search box with autocomplete and the results page. Yes to mobile. Skip i18n for now."
๐๐ Candidate: "Let me break this down into components..."
High-Level Architecture
App|+-- Header| +-- Logo| +-- SearchBox| | +-- SearchInput| | +-- AutocompleteDropdown| | +-- VoiceSearchButton| | +-- SearchButton| +-- UserMenu|+-- HomePage| +-- SearchBox (centered)| +-- QuickLinks|+-- ResultsPage +-- SearchBox (top) +-- Filters (tabs) +-- ResultsList | +-- ResultItem | +-- FeaturedSnippet | +-- PeopleAlsoAsk +-- Pagination +-- RelatedSearches
๐๐ค Interviewer: "How would you implement the autocomplete?"
Candidate's Answer:
const useAutocomplete = (query) => { const [suggestions, setSuggestions] = useState([]); const [isLoading, setIsLoading] = useState(false); const [cache, setCache] = useState(new Map()); const abortControllerRef = useRef(null); const debouncedQuery = useDebounce(query, 150); useEffect(() => { if (!debouncedQuery || debouncedQuery.length < 2) { setSuggestions([]); return; } // Check cache first if (cache.has(debouncedQuery)) { setSuggestions(cache.get(debouncedQuery)); return; } // Cancel previous request if (abortControllerRef.current) { abortControllerRef.current.abort(); } abortControllerRef.current = new AbortController(); const fetchSuggestions = async () => { setIsLoading(true); try { const response = await fetch( `/api/autocomplete?q=${encodeURIComponent(debouncedQuery)}`, { signal: abortControllerRef.current.signal } ); const data = await response.json(); // Update cache setCache(prev => new Map(prev).set(debouncedQuery, data)); setSuggestions(data); } catch (error) { if (error.name !== 'AbortError') { console.error('Autocomplete failed:', error); } } finally { setIsLoading(false); } }; fetchSuggestions(); }, [debouncedQuery]); return { suggestions, isLoading };};
๐๐ค Interviewer: "What edge cases are you handling?"
Candidate:
1. Race Conditions
// User types "react" fast: r -> re -> rea -> reac -> react// Without abort, we might show results for "re" after "react"// Solution: AbortController cancels stale requests2. Empty/Invalid Input
if (!query.trim()) return [];if (query.length < 2) return []; // Min charsif (query.length > 200) return []; // Max chars3. Special Characters
const sanitizedQuery = encodeURIComponent(query);// Handles: spaces, &, ?, #, unicode4. Network Failures
const [error, setError] = useState(null);const [retryCount, setRetryCount] = useState(0);// Retry with exponential backoffif (error && retryCount < 3) { setTimeout(() => fetchSuggestions(), Math.pow(2, retryCount) * 1000);}5. Keyboard Navigation
const [selectedIndex, setSelectedIndex] = useState(-1);const handleKeyDown = (e) => { switch (e.key) { case 'ArrowDown': e.preventDefault(); setSelectedIndex(i => Math.min(i + 1, suggestions.length - 1)); break; case 'ArrowUp': e.preventDefault(); setSelectedIndex(i => Math.max(i - 1, -1)); break; case 'Enter': if (selectedIndex >= 0) { selectSuggestion(suggestions[selectedIndex]); } else { submitSearch(query); } break; case 'Escape': setSuggestions([]); setSelectedIndex(-1); break; }};
๐๐ค Interviewer: "How do you handle the results page with infinite data?"
Candidate: "I'd use virtualization for performance..."
const VirtualizedResults = ({ results }) => { const [visibleRange, setVisibleRange] = useState({ start: 0, end: 20 }); const containerRef = useRef(); const ITEM_HEIGHT = 180; useEffect(() => { const container = containerRef.current; const handleScroll = () => { const scrollTop = container.scrollTop; const viewportHeight = container.clientHeight; const start = Math.floor(scrollTop / ITEM_HEIGHT); const end = Math.ceil((scrollTop + viewportHeight) / ITEM_HEIGHT) + 5; setVisibleRange({ start, end: Math.min(end, results.length) }); }; container.addEventListener('scroll', handleScroll, { passive: true }); return () => container.removeEventListener('scroll', handleScroll); }, [results.length]); const visibleResults = results.slice(visibleRange.start, visibleRange.end); const totalHeight = results.length * ITEM_HEIGHT; const offsetY = visibleRange.start * ITEM_HEIGHT; return ( <div ref={containerRef} style={{ height: '100vh', overflow: 'auto' }}> <div style={{ height: totalHeight, position: 'relative' }}> <div style={{ transform: `translateY(${offsetY}px)` }}> {visibleResults.map((result, index) => ( <ResultItem key={result.id} result={result} style={{ height: ITEM_HEIGHT }} /> ))} </div> </div> </div> );};
๐๐ค Interviewer: "What about accessibility?"
Candidate:
<div role="combobox" aria-expanded={isOpen} aria-haspopup="listbox"> <input type="search" aria-label="Search" aria-autocomplete="list" aria-controls="suggestions-list" aria-activedescendant={selectedIndex >= 0 ? `suggestion-${selectedIndex}` : undefined} /> {isOpen && ( <ul id="suggestions-list" role="listbox" aria-label="Search suggestions"> {suggestions.map((item, index) => ( <li key={item.id} id={`suggestion-${index}`} role="option" aria-selected={index === selectedIndex} > {item.text} </li> ))} </ul> )}</div>
๐๐ Interview Scoring
Result: Strong Hire ๐