Design search autocomplete
Suggest completions as the user types, within a tenth of a second: tries, top-k per prefix and how suggestions are updated.
On the roadmap:Interview Ready · step 4: Media and storage
Lesson 9 of 11 · 10 min read · quiz · exercise
The problem
As a user types "syst", show a handful of likely completions: "system design", "systemd", "system of a down". The suggestions must appear almost instantly (under about 100 ms) or they are useless, and they should reflect what people actually search.
Requirements
- Fast: respond within ~100 ms.
- Return the top 5 to 10 completions for the typed prefix, ranked by popularity.
- Handle high volume (a request per keystroke).
- Updated regularly with new trends.
The core data structure: a trie
A trie (prefix tree) stores words along paths of letters, so all words that share a prefix share a path.
Figure SDA-CSD-09-1 · Trie with word frequencies · © forcsguy.com
To find completions for a prefix, walk to its node, then look at everything beneath it. Doing that on every keystroke is too slow for a big trie, so we precompute: at each node store the top k most popular completions for that prefix.
Figure SDA-CSD-09-2 · Top-k completions cached per prefix · © forcsguy.com
Now a query is: walk the prefix (a few steps), read the stored list. That is fast and independent of how large the subtree is.
From raw queries to counts
Figure SDA-CSD-09-3 · Counting queries into a frequency table · © forcsguy.com
Two services
Figure SDA-CSD-09-4 · Online query path and offline trie build · © forcsguy.com
Query service (online)
- Receive the prefix.
- Look it up in the in-memory trie or a prefix cache.
- Return the top suggestions.
Data pipeline (offline)
- Search queries are logged.
- A periodic job (hourly or daily) aggregates counts per query, filtering out spam and abusive terms.
- A builder creates the new trie with top-k lists.
- The new version is published to the serving machines and swapped in.
Real-time updates for every search would be costly and unstable. For breaking trends you can add a small fast path for recent spikes.
Looking up a prefix in the trie
Figure SDA-CSD-09-5 · Autocomplete lookup on a trie with cached top lists · © forcsguy.com
When one query's count changes, every node on the path above it must refresh its stored list, which is why the trie is rebuilt in a batch rather than edited per search:
Figure SDA-CSD-09-6 · Updating a count touches all ancestors · © forcsguy.com
Figure SDA-CSD-09-7 · A trie stored as key-value pairs · © forcsguy.com
Figure SDA-CSD-09-8 · Filtering unwanted suggestions · © forcsguy.com
Figure SDA-CSD-09-9 · Sharding the trie by prefix · © forcsguy.com
Making it faster and bigger
- Client side: wait a short pause after typing (debounce), skip very short prefixes, and cache previous responses in the browser.
- Server side: keep the trie in memory; cache hot prefixes; set a CDN or edge cache for the most common ones.
- Limit prefix length (for example 50 characters) to bound work.
- Shard the trie across machines, for example by the first letters, balancing by traffic not by alphabet (some letters are far more common).
- Replicate the serving tier for availability.
The numbers and the trie's cost
Each keystroke is a request, so 10 million users doing 10 searches of about 20 keystrokes is on the order of 24,000 requests per second (about double at peak), while only roughly 0.4 GB of new query text arrives per day. The trie's walk to a prefix is proportional to the prefix length, then reading its children and sorting them would be slow; two changes make lookup effectively constant time: cap the prefix length, and store the top few suggestions on every node (trading space for time). The cost moves to updates: changing one count means refreshing the cached lists on every ancestor up to the root, which is why the trie is normally rebuilt in a batch (say weekly) from aggregated logs and swapped in, rather than edited per query.
Storing the trie. Either serialise each weekly snapshot into a document store, or flatten it into a key-value store where each prefix is a key and the node's data is the value. Serve from a distributed cache; on a miss, fill the cache from the store.
Log less. Sample the query log (record one in N) because logging every keystroke-level request is costly and top results are statistically stable.
Take-down. Put a filter layer in front of the cache to hide unsafe suggestions immediately, and delete them from the data before the next rebuild.
Variants. Different countries can have their own tries (even served from a CDN); other languages need Unicode in the nodes; trending queries need a faster path, such as stream processing and a ranking that weights recent searches more.
Quality details
- Ranking can combine popularity, recency and personalisation.
- Safety: filter offensive or sensitive suggestions.
- Typos: optionally tolerate small spelling errors.
- Languages: tokenisation and ordering differ per language, so plan for it early if you need it.
Worked numbers: does the whole thing fit in memory?
Say the system serves the 10 million most common prefixes, storing the top 5 suggestions for each at roughly 100 bytes (five short strings plus scores). That is about 1 GB, comfortably inside one machine's memory, and a few GB with overhead. Even 100 million prefixes is only about 10 GB. So the "answer from memory, rebuilt in the background" design is realistic; no search over a huge dataset happens per keystroke.
Latency budget for a 100 ms target as seen by the user: the browser debounce is an intentional pause (not part of the budget); network round trip 30 to 50 ms; edge or load balancer 5 ms; service 5 to 10 ms to hash the prefix and read the cached list; response and render 10 ms. There is little room for anything slower, which is why the lookup must be a single key read.
Request volume: 10 million users x 10 searches x about 20 keystrokes is 2 billion prefix lookups a day, about 24,000 per second on average. Debouncing (waiting about 150 ms after the last keystroke) and not querying for very short prefixes can cut that by half or more.
Figure SDA-CSD-09-10 · Debounce and ignore late answers · © forcsguy.com
Ranking: popular, recent and personal
Raw popularity is a good start but has two problems. It favours old favourites over trending queries, and it is the same for everyone. A common improvement blends signals:
score = popularity x decay + trending boost + personal boost
- Decay: weight recent searches more, for example by halving a query's weight every day or week, so a sudden event rises quickly and fades.
- Trending: a fast path watches a stream of recent queries and lifts those whose rate jumps compared with their normal level.
- Personal: a user's own recent searches, kept separately (and cached per user, not in the shared trie).
- Safety and quality: filter offensive or illegal suggestions and anything with too few searches to be useful or private.
Figure SDA-CSD-09-11 · Blending slow and fast ranking signals · © forcsguy.com
Mistakes people make
- Searching the data per keystroke. Precompute the top suggestions per prefix.
- Ignoring out-of-order responses. A slow response for "sy" arriving after "syst" overwrites the list with stale results.
- Putting personal data in the shared cache. Keep personal suggestions out of anything shared across users.
- Forgetting normalisation. Case, accents, extra spaces and different Unicode forms must map to the same prefix.
- No filtering. Autocomplete amplifies whatever people type, including offensive or private terms.
- Rebuilding on every search. Rebuild in batches and swap versions atomically.
Try it yourself
Build the tiny search engine lab for the index side, and read the query understanding lesson for how typed text is cleaned before it is matched.
Real-world reading
Related reading
- YouTube ArchitectureHigh ScalabilityCase Studies
- Hello Interview: System Design in a HurryHello InterviewCourses
- Hello Interview: System Design WalkthroughsEvan King & Stefan Mai · Hello InterviewYouTube
- Meta Engineering BlogMeta Engineering · MetaBlogs
Show 6 moreShow fewer
- Cloudflare Learning Center: What is a CDN?CloudflareDocumentation
- Solving Remote Build Cache Misses (2 parts) at SoundCloudSoundCloud engineers · SoundCloudCase Studies
- Scaling Memcache at Facebook (paper)Nishtala et al. · Meta (Facebook)Papers
- Reduce Memcached Memory Usage by 50% at TrivagoTrivago engineers · TrivagoCase Studies
- Amazon Architecture - High Scalability -High Scalability engineers · High ScalabilityCase Studies
- LINE Engineering BlogLINE engineers · LINEBlogs
Check your understanding
Question 1 of 3
0 correct so far
Why store the top suggestions at each trie node instead of searching the subtree on every keystroke?
Go deeper
- Introduction to Information Retrieval, chapter 3 (tolerant retrieval) by Manning, Raghavan and Schutze: free online textbook
- Prefix hash tree: an indexing data structure over distributed hash tables by Berkeley: a way to spread a prefix index across nodes
- Unicode frequently asked questions by Unicode Consortium: what to know before supporting other languages
Practise it: Autocomplete at 8,000 requests a second
Everything below is already set up for this lesson: change the design, run the traffic, and see whether you meet the goal.
- 1The task
- 2Build and test
Every keystroke asks for suggestions for a prefix, and most prefixes repeat constantly.
Goal: Under 1% errors, a slowest 5% under 150 ms, database below 40%.
Suggestions for a prefix change slowly. Keep the answers close.
Try it yourself
Estimate the query rate for autocomplete: 10 million daily active users, each making 10 searches a day, typing about 20 characters per search. What is the average request rate if a request is sent for each character, and how would you reduce it?
Click what a strong design here would need:
More in Design practice
© 2026 forcsguy.com. Original lesson text and diagrams. Books and sites linked above belong to their authors.