InterviewKata
System design · Worked design D10 of 46 · Social and content · Long form

Design search typeahead

Type "how to" into a search box and ten suggestions appear before your next keystroke. Every keystroke is a request, so a search engine serves far more suggestion lookups than searches, and each must answer in a few milliseconds. The trick is to do almost no work at query time: precompute the answer for every popular prefix in advance, and serve it from memory. It follows the interview framework phase by phase.

Modules this design uses09 Caching · 12 Indexes · 23 Batch and stream · 03 Estimation · 10 CDNs

1. The prompt

"Design the autocomplete for a search box: as the user types, show the top 10 most popular queries that start with what they've typed."

The read path must be almost free, so the design moves the work offline: a pipeline that counts queries and precomputes answers, and a serving fleet that only looks them up. The interesting parts are the data structure, freshness for trending queries, and taking load off the servers altogether with the client and the CDN.

2. Frame the problem

Suggestions from what: past searches, or a product catalogue?

Popular past search queries.

We need a pipeline from search logs to counts to precomputed lists.

How fresh must popularity be?

Daily for most; trending queries within minutes.

A batch build plus a small streaming layer.

How many suggestions, and how fast?

Up to 10, within ~50 ms end to end.

A few milliseconds on the server leaves room for the network.

Personalised?

Lightly: the user's own recent searches mixed in.

A small per-user layer on top of global lists, done on the device or a small store.

Must anything be filtered?

Yes: offensive or unsafe suggestions must never appear.

Filtering at build time, plus an emergency block list at serving time.

Languages and regions?

Per locale; we'll treat each locale as its own table.

The tables partition naturally by locale.

3. What it must do

  1. For a prefix, the system returns up to 10 suggestions, ranked by popularity.
  2. Popularity reflects recent searches: trending queries appear within minutes.
  3. Operators can block offensive or unsafe suggestions immediately.

Not now:

4. How well it must do it

TargetWhy this number
Very fast: p99 under 50 ms end to end; a few ms at the server speedSuggestions are useless if they arrive after the next keystroke.
Scale: ~75,000 lookups a second at peak before caching load shapeEvery keystroke is a request.
Fresh: trending queries within ~10 minutes; everything else daily speedPeople search for breaking news immediately.
Available: stale suggestions are fine; no suggestions is an acceptable fallback consistency or availabilitySearch still works without autocomplete.
Safe: a blocked suggestion disappears everywhere within minutes security · rulesAn offensive suggestion is a public embarrassment.

5. Numbers that change the design

QuantityWorking→ so we…
Lookups500M searches a day × 5 keystrokes ÷ 10⁵ s~25,000/s, ~75,000/s at peak → so cut requests at the client and CDN first
Distinct queries worth suggestingQueries seen at least ~10 times in the window~100M queries
Prefix tablePrefixes up to 20 characters, ~10 suggestions each, heavily sharedTens of GB → so it fits in memory across a few servers, replicated
Short prefixes"h", "ho", "how" are requested by everyone→ so they cache superbly at the edge

6. The nouns

query log      text, time, locale                      (stream → data lake)
query counts   (locale, query) → decayed score         (batch job output)
prefix table   (locale, prefix) → [(query, score) × 10]   versioned, loaded whole into memory
block list     terms and exact queries, applied at build and at serve time

7. The contract

GET /v1/suggest?q=how%20to%20m&locale=en-IN&limit=10
→ 200 {"suggestions": ["how to make pancakes", "how to meditate", …]}
   Cache-Control: public, max-age=300

Anonymous and cacheable: personal suggestions (the user's own history) are mixed in on the device, so the shared response stays cacheable.

Data flow: (1) searches are logged → (2) a batch job counts and builds the prefix table → (3) servers load the new version → (4) a stream job adds trending queries → (5) the client debounces and caches; the CDN answers popular prefixes; misses hit a server's memory.

8. First working version

A queries table with counts, updated from the search logs, and a suggestion service that queries it.

8.1 Suggesting

For each keystroke, the service runs a prefix query sorted by count.

8.2 Counting

A nightly job aggregates the day's search log into the counts table.

8.3 Blocking

A blocked flag on the queries row hides it from the query.

Parked for later (section 9 fixes each)

  1. A short prefix like "h" matches millions of rows; sorting them on every keystroke is far too slow.
  2. How to rebuild without serving a half-built table.
  3. 75,000 requests a second, most of them for the same short prefixes.
  4. Nightly counts miss breaking news.
  5. Personal history and safety.

9. Make it hold up

9.1 Finding the top suggestions for a prefix very fast

ApproachWhy
✗ BadSELECT query FROM queries WHERE query LIKE 'how to m%' ORDER BY count DESC LIMIT 10The prefix match can use an index, but sorting all matches by count on every keystroke is far too slow for short prefixes like "h", which match millions of rows.
~ GoodA trie (prefix tree): walk down to the prefix's node, then search its subtree for the most popular completionsWalking down is fast (one step per character), but searching a big subtree at query time is still slow for short prefixes.
✓ GreatA trie where every node stores its own top 10, computed when the trie is built; or simply a hash map from prefix to its top 10A lookup is one walk (or one hash lookup) and returns a ready list. All the work moves into the offline build. Try it in section 13.

9.2 Building the tables, and swapping safely fresh · available

  1. Search events stream into a log (Kafka) and land in the data lake.
  2. A batch job (hourly or daily) counts queries over a window, with more weight for recent searches (exponential decay), drops rare queries, and filters out blocked terms.
  3. It builds the prefix → top-10 table for every prefix of every kept query.
  4. The new table is loaded into the serving fleet as a whole new version, and servers switch to it atomically. Serving never sees a half-built table; a bad build is rolled back by switching back.

9.3 Serving it fast, and mostly not serving it at all very fast · scale

9.4 Trending queries fresh

A daily rebuild misses a story breaking right now. Add a stream job that counts queries over the last few minutes (Module 23) and detects sudden rises; a small "trending" table is merged into results at query time, or into the top lists of affected prefixes. Keep CDN lifetimes short (a few minutes) so trends reach users.

9.5 Personalisation and safety safe

Blend global suggestions with the user's own recent searches, kept on their device or in a small per-user store, so the shared response stays cacheable. Filter suggestions through a block list and safety classifiers at build time, plus an emergency block list applied at serving time for anything that slipped through. An emergency block also purges affected prefixes from the CDN.

9.6 The whole design after the deep dives

Building, swapping and serving suggestions in the final design.

10. Interviewer follow-ups

Traffic grows 10×.

Most of it lands on the CDN and client caches. Add server replicas for the misses. The build job grows with data volume, but it's offline.

Support typos ("hwo to").

At build time, also index common misspellings that lead to the same query (from logs where people corrected themselves), or run a fuzzy match on short edit distances for prefixes with few results.

Match words in the middle ("pancakes" → "how to make pancakes").

Index every word position, not just the start: each query is added under the prefixes of each of its words. The table grows several times, still within memory for popular queries.

A bad build ships offensive suggestions.

Roll back to the previous version (an atomic switch), purge the CDN, add the terms to the emergency list, and add a pre-swap check that compares the new version's top prefixes with the old one.

How do you measure quality?

Acceptance rate (how often a suggestion is clicked), keystrokes saved, and latency. A/B test scoring changes.

Trie or hash map?

A hash map of prefix → list is simplest to serve and shard; a trie saves memory by sharing prefixes. With precomputed lists, either works; pick the hash map unless memory is tight.

Different results per country.

Build a table per locale (or region), and pass the locale in the request. CDN caches key on it.

11. When things break

FailureWhat users seeHow we detect itWhat limits the damage
Build job failsSlightly stale suggestionsTable age alertServers keep the last good version
Bad buildOdd or offensive suggestionsPre-swap checks, acceptance rate drop, reportsAtomic rollback, CDN purge, emergency block list
Suggest servers overloadedSlow or missing suggestionsLatency, error rateCDN and client caches absorb most; the client hides suggestions on timeout
Trending stream lagsNew trends appear laterConsumer lagDaily build still covers the rest

12. Wrap-up and the clock

Trade-offs we made:

What we'd watch:

At 10×:

A 45-minute interview

0–4Frame the problem (source, freshness, safety)
4–9Must do, how well, numbers
9–12Nouns, contract, data flow
12–18First working version, parked list
18–40Deep dives: trie with top-k (9.1), build and swap (9.2), client/CDN (9.3), trending (9.4)
40–45Follow-ups and wrap-up

A 60-minute interview

0–5Frame the problem
5–11Must do, how well, numbers
11–15Nouns, contract, data flow
15–23First working version
23–50Deep dives 9.1–9.5 and the final picture
50–60Follow-ups: typos, mid-word matching, bad builds; wrap-up
LevelWhat a strong answer shows
Mid-levelA trie, why the naive query is slow, serving from memory, a periodic rebuild from logs.
SeniorTop-k stored per node, an atomic swap of new versions, client debouncing and caching, CDN for short prefixes, balanced partitioning.
Staff+Time-decayed scoring, the trending stream, personalisation blend, safety filtering, and the cost trade-off between build frequency and freshness.

Common mistakes:

13. Try it, check yourself, read more

A real trie built from about 40 sample queries with counts. Each node stores its top 5. Type a prefix: the lookup walks one node per character and returns the stored list.

How do you make a trie lookup fast even for a one-letter prefix?

The expensive part moves offline; a lookup just reads the stored list.

What does debouncing do?

Fast typists send one request instead of five, cutting load substantially.

How do breaking-news queries appear quickly?

A small, fast trending table complements the slower full rebuild.

How do servers move to a new table without ever serving a half-built one?

Versioned tables make the swap instant and the rollback just as easy.
ResourceWhy
Design a search autocomplete system (ByteByteGo)A full chapter on the same design.
Trie (Wikipedia)The data structure, with variants such as compressed (radix) tries.