๐Ÿ‡ฎ๐Ÿ‡ณ
๐Ÿ‡ฎ๐Ÿ‡ณ
Republic Day Special Offer!Get 20% OFF on all courses
Enroll Now
P
Prakalpana
๐Ÿ“šLearn
โ€ขCode Your Future
Interview Prepโฑ๏ธ 20 min read๐Ÿ“… Dec 24

UI Interview: Design Google Search (Complete Mock)

VP
Vikram Patelโ€ขStaff Engineer at Google
๐Ÿ“‘ Contents (14 sections)

๐Ÿ“Œ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:

  • 1Are we focusing on the search box, results page, or both?
  • 2Should I consider mobile responsiveness?
  • 3What about features like autocomplete, voice search, image search?
  • 4Do we need to handle internationalization?
  • 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 requests

    2. Empty/Invalid Input

    if (!query.trim()) return [];
    if (query.length < 2) return []; // Min chars
    if (query.length > 200) return []; // Max chars

    3. Special Characters

    const sanitizedQuery = encodeURIComponent(query);
    // Handles: spaces, &, ?, #, unicode

    4. Network Failures

    const [error, setError] = useState(null);
    const [retryCount, setRetryCount] = useState(0);
    // Retry with exponential backoff
    if (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

    CriteriaScoreNotes

    Requirements Gatheringโœ…Asked clarifying questions Component Architectureโœ…Clean hierarchy State Managementโœ…Proper hooks usage Edge Casesโœ…Covered race conditions, errors Performanceโœ…Virtualization, caching Accessibilityโœ…ARIA attributes

    Result: Strong Hire ๐ŸŽ‰

    VP

    Written by

    Vikram Patel

    Staff Engineer at Google

    ๐Ÿš€ Master Interview Prep

    Join 500+ developers

    Explore Courses โ†’
    Chat on WhatsApp