Design Typeahead / Search Autocomplete
mediumTypeahead is the list of suggestions that appears while you type in a search box. It has to appear before you finish typing, which means answering in well under a tenth of a second.
Key Concepts
youtub overwhelmingly went on
to accept youtube. That gives you a lookup table. One extra lookup, no searching.High-level design
Offline batch: query logs → Spark aggregator → top-K per prefix → trie builder → trie store.
Streaming: query logs → Flink → delta updates → push to trie (every few minutes).
Serving: user prefix → trie lookup → top-K candidates → edge rerank for personalization → response.
Misspelling: separate lookup; replace prefix with corrected form before trie walk.
Components
- Query log aggregator (Kafka + Spark).
- Trie builder (batch + incremental).
- Serving fleet (in-memory trie shards, sharded by first 1-2 chars).
- Edge cache (CDN) for short prefixes (huge hit rate).
- Personalization service (per-user history, session context).
- Streaming pipeline for trending detection.
- Misspelling correction service.
- Feedback logging — which suggestion the user picked, for ranking refinement.
Latency budget (worked)
Worked latency budget -- 100 ms, mobile user, same-region server.
Network round trip (4G, in-region) 30-60 ms TLS resumption + request parsing 5-10 ms Client render of the dropdown 5-10 ms ------------------------------------------------ Remaining for your server 20-40 ms
Inside that remainder:
Shard lookup (in-memory trie walk) < 1 ms Top-K read from the node < 1 ms Edge re-rank over 10 candidates 1-2 ms Everything else is headroom for p99 spikes.
Two consequences worth stating in the room:
- A cross-region hop (150 ms+) blows the budget on its own, so the serving
tier has to sit near the user. This is the argument for edge placement,
and it is stronger than "edge is faster".
- Anything that touches disk or a second service inside the request is out.
That includes fetching a user profile, which is why personalisation
re-ranks what the shard already returned.
Index sizing (worked)
Index sizing -- redo this with your own assumptions.
Distinct queries per month 500,000,000 Kept after pruning (seen >= 5 times) 50,000,000 Prefix nodes after sharing (len 1-20) ~200,000,000 Top-K stored per node 5 Bytes per completion (string + score) 30 Payload per node 5 x 30 = 150 bytes ------------------------------------------------------------ Index size 200M x 150 B = ~30 GB
Per shard, at 16 shards ~2 GB
With 3 replicas 48 machines' worth of RAM,
though a 2 GB shard shares
a host comfortably.Change one assumption and the shape changes:
- Pruning at >= 50 occurrences instead of 5 cuts the index by roughly an
order of magnitude, at the cost of long-tail coverage.
- Storing top 10 instead of top 5 doubles payload for a ranking gain most
users never see, since the dropdown shows 5-8 rows.
Failure modes in production
Failure modes that come up once this is running.
Hot shard on a common prefix
Symptom: p99 climbs on a subset of traffic while p50 looks fine.
Cause: shard boundaries drawn on the alphabet, not on traffic.
Fix: rebalance by measured share; keep the map versioned so serving nodes
can reload without a deploy. Trending term swamps the index
Symptom: durable completions disappear during a spike, return later.
Cause: streaming deltas overwrite rather than add.
Fix: additive boost with decay, and a floor on the batch-computed entries. Stale index after a failed batch
Symptom: suggestions quietly age; nobody notices for days.
Cause: no freshness signal on the serving tier.
Fix: serve the build timestamp with the response and alert on its age --
the cheapest observability in the whole design. Empty results for a valid prefix
Symptom: a real query returns nothing as the user passes a certain length.
Cause: pruning removed the tail, and there is no fallback.
Fix: fall back to the longest prefix that does have completions, rather
than returning an empty list.