Design Autocomplete / Typeahead
Frontend system design for search typeahead — client UX, API, caching, ranking surface, and a11y at scale.
- system-design
- search
- performance
- caching
- interview
Scope the problem
Autocomplete interviews span UI component and system design. Clarify which hat they want; usually it’s frontend-heavy with API contracts and light backend awareness.
In scope:
- Query input + suggestion dropdown
- Debounce, cancelation, keyboard UX, a11y
- Client cache and request coalescing
- API shape, pagination of suggestions, empty/error
- Personalization hooks (recent, trending) at a high level
- Latency budget and ranking surface (not ML training)
Out of scope unless asked:
- Full search index sharding
- Spell-correction model training
- Multi-region failover diagrams for 20 minutes
Scale assumptions:
- Millions of queries/day; p95 suggest latency < 100–150ms edge-to-client ideal
- Mobile keyboards; flaky networks
- Multi-entity results (people, pages, queries)
Requirements
| Type | Detail |
|---|---|
| Functional | Suggest as you type; select navigates or fills; recent searches |
| Non-functional | Low input lag, race-safe results, accessible combobox |
| Privacy | Don’t leak queries to third parties; careful logging |
Product questions to ask: search-as-you-type vs submit-only? Multi-select tokens? Vertical-specific (e.g. maps places)?
High-level architecture
┌─────────────┐ debounced q ┌──────────────────┐
│ Combobox │ ───────────────────► │ Suggest API │
│ UI + cache │ ◄─────────────────── │ (edge / BFF) │
└─────────────┘ ranked suggestions └────────┬─────────┘
│
┌──────────▼──────────┐
│ Prefix index / │
│ search service │
└─────────────────────┘
Frontend owns: UX state machine, caching, abort, rendering, analytics. Backend owns: index, ranking, authz for private entities.
Client component design
Mirror the machine-coding brief: Autocomplete Search Box.
Typeahead
├── Input (combobox)
├── Panel (listbox)
│ ├── Section: Recent
│ ├── Section: Suggestions
│ └── Section: Trending (optional)
└── Status (loading / empty / error)
State machine (simplified)
idle → typing → debouncing → loading → results | empty | error
with transitions for select, escape, blur (careful: blur vs option mousedown).
Client API
type SuggestRequest = {
q: string;
limit?: number;
lang?: string;
sessionId?: string; // personalization without always sending user id
};
type SuggestItem = {
id: string;
type: "query" | "user" | "page" | "place";
label: string;
sublabel?: string;
score?: number;
};
type SuggestResponse = {
items: SuggestItem[];
requestId: string;
};
Latency & correctness (frontend)
| Technique | Why |
|---|---|
| Debounce 150–300ms | Cut QPS; feel still snappy |
| Min chars (1–2) | Avoid useless traffic |
AbortController |
Stale responses never paint |
| In-flight map by query | Coalesce identical concurrent |
| LRU cache of responses | Backspace feels instant |
| Prefetch on hover of global search icon | Optional warm-up |
Race rule: only apply results if response.q === latestQuery (or abort). This is a non-negotiable talking point.
Caching strategy
| Layer | Key | TTL / policy |
|---|---|---|
| Memory LRU | exact q + locale |
Session; capacity ~50–100 |
| HTTP cache | GET /suggest?q= |
Short max-age or private no-store if personalized |
| Persistent | recent user selections | Local only; capped |
Personalized suggests: prefer Cache-Control: private and client memory over shared CDN cache.
Ranking surface (what FE needs)
Discuss as a product API, not ML:
- Prefix / n-gram text match
- Popularity prior
- User recent + graph affinity
- Entity type boosts (people first in a people search)
UI may group by type; don’t re-sort server ranking arbitrarily unless product requires.
Accessibility & i18n
- Combobox APG pattern:
aria-expanded,aria-activedescendant, listbox options - Highlight matches without breaking screen reader labels
- RTL layout for panel
- IME composition: don’t debounce-fire mid-composition
- Announce result count via polite live region
Performance budgets
- Input handler work < few ms; no heavy JSON parse on main thread for huge payloads (limit server-side)
- Panel render: virtualize only if 50+ options (usually 5–10)
- Bundle: keep typeahead in the header chunk or lazy when search is secondary
Observability
- Client: time-to-first-suggest, abandon rate, select-through rate, error rate
- Correlate with
requestId - Watch for “empty rate” spikes after deploys
Tradeoffs
- Client filter of static list vs server suggest — client only for tiny dictionaries
- Aggressive cache vs freshness of trending
- SSE/WebSocket push of trending vs pull
- Native datalist vs custom combobox — datalist fails most product UX
- BFF aggregation of multi-vertical search vs parallel FE calls
Interview delivery (45 min)
- 0–5m Scope, users, latency goals
- 5–15m UI architecture + a11y
- 15–25m API + race/debounce/cache
- 25–35m Backend sketch + ranking surface
- 35–45m Perf, privacy, tradeoffs, metrics