System designby Learnastra

System-design interview · Core interviews

Design a typeahead service

By Anup Rai

Build fast prefix suggestions by moving ranking work into a versioned index while keeping browser input, privacy and removal rules correct.

You will learn to

  • Explain trie lookup and why precomputed candidate lists bound query work.
  • Choose coherent ranking snapshots and account for their memory and freshness costs.
  • Prevent stale browser responses and private history from contaminating public suggestions.

Practice in this chapter

8 interview questions with model answers and follow-ups.

Go to interview practice

Useful foundations: Database indexes: B-trees, composite keys and query access · Caching: cache hits, misses, write policies and invalidation · Data partitioning and sharding

Workload and timing examples are interview assumptions.

01Define what a suggestion means

Design a service that suggests up to ten approved search terms while a person types. The request “ca” can match “cat,” “capital,” “captain,” “caption” and “cap.” Rank matching terms by recent submitted-search popularity, with deterministic ties. Suggestions should arrive quickly enough to remain useful while typing; use an illustrative 200 ms end-to-end target.

Choose exact prefix matching for the first design. Typo correction, arbitrary substring search and semantic similarity need additional candidate generators. A prefix index does not acquire those capabilities because it has more replicas. Require at least two characters, bound input length and keep ordinary search submission usable when suggestions fail.

The public vocabulary is approved separately from private search history. Index and query processing share one documented Unicode normalization and locale-aware case policy while preserving display spelling. This prevents predictable mismatches between stored terms and input; it is not a complete defense against visually confusing characters.

Use hourly popularity updates. Urgent blocked-term removal uses a current serving-time policy check, independent of the hourly index. This worked design does not promise a worldwide removal deadline or control words already displayed on an offline browser; those stronger contracts belong in a later discussion.

Clarify exact-prefix versus typo-tolerant matching, the ranking signal, acceptable popularity delay and the behavior during policy-service failure. The following contract chooses exact prefixes and safe empty suggestions when current approval cannot be established.

02Functional requirements

Agree on these supported actions before selecting components.

  1. Return ranked prefix matches. Given at least two input characters and a locale, return up to ten approved matching terms ordered by recent submitted-search popularity and deterministic ties.

  2. Handle changing input. Use compatible normalization in indexing and queries, preserve display spelling and render only responses belonging to the search box’s current input sequence.

  3. Update popularity and policy. Ingest identified submitted-search events, rebuild popularity snapshots and remove blocked terms from final candidate results.

  4. Keep ordinary search usable. Return fewer or no suggestions when necessary without preventing query submission. Private history, when present, remains user-scoped rather than entering a public response cache.

03Non-functional requirements

Use these as illustrative interview assumptions to agree with the interviewer. Numerical targets require measurement; they are not claims about an existing product or a proven implementation. p95 (the 95th percentile) means 95% of measured operations finish within the stated time.

  1. Workload and memory. Plan for approximately 1.16 million suggestion requests/s at the fivefold peak. The illustrative 40 GB snapshot may need 80 GB during replacement before process reserve; validate these assumptions with representative builds.

  2. Response latency. Target p95 end-to-end suggestion latency of 200 ms after the browser sends a request on the declared regional client/network profile at admitted peak load. Debounce waiting before send is measured separately, not hidden inside server time.

  3. Ranking freshness. Publish popularity snapshots hourly from a fixed input cutoff using the selected ten-day count window. Delayed or failed builds retain the previous validated snapshot and expose its age.

  4. Coherent and rebuildable state. A query uses one complete snapshot version. Durable vocabulary and count inputs survive a process restart or one database-node failure; serving indexes can be rebuilt and hot ranges replicated.

  5. Current approval and privacy. Check final candidates against current policy after all cache and index sources. If that check fails, return no suggestions. Do not promise a worldwide removal deadline or recall of words already displayed.

  6. Bounded request cost. Limit input length, candidates, shard fanout and retained raw events. Candidate ranking must remain meaningful under abuse controls rather than letting repeated malicious submissions dominate counts.

04Find and rank five words by hand

A trie is a tree in which following successive character edges locates a prefix. Traversing c, then a, reaches the subtree containing the five example words. A terminal marker identifies a complete term: cap is both a valid word and the start of capital, captain and caption.

Build this small index from a durable vocabulary and counts. At query time, traverse the prefix, enumerate descendant terminal terms, sort them by popularity and return the first ten.

Term Submitted-search count Order for ca
cat 900 1
capital 700 2
captain 500 3
caption 400 4
cap 100 5

For cap, cat does not match and must disappear regardless of its higher score.

The browser waits for a short pause in typing before sending a request; this is debouncing. Give each input change an increasing request sequence. If ca is request 12 and cap is request 13, only the response matching the current sequence may update the screen. Canceling request 12 can save work, but it cannot guarantee that an already completed response will not arrive.

This baseline works with one application and one in-memory index rebuilt from durable data. A database prefix query would also work for a small vocabulary. The reason to evolve the trie is measurable query work, not that databases are intrinsically unsuitable for suggestions.

Design diagramA complete small prefix service

The serving application reads a trie built from durable approved terms and counts.

A complete small prefix serviceThe serving application reads a trie built from durable approved terms and counts. browser to api: Prefix + request sequence; api to index: Find and rank matches; source to index: Build indexPrefix + request sequenceFind and rank matchesBuild indexACTORSearch boxSERVICESuggestion APISTOREIn-memory trieSTOREVocabulary andcountssyncasync
Read each connection in order
  1. syncPrefix + request sequenceSearch box → Suggestion API
  2. syncFind and rank matchesSuggestion API → In-memory trie
  3. asyncBuild indexVocabulary and counts → In-memory trie

05Budget the index, not only its strings

Assume five billion submitted searches/day. If each submission generates four suggestion requests after debounce, the serving average is approximately 231,481 requests/s, with a fivefold peak near 1.16 million/s. Counting submitted searches alone understates the load.

Quantity Calculation Consequence
Submitted searches 5 billion / 86,400 ≈ 57,870/s Popularity events can be processed asynchronously.
Stored strings 100 million terms × 30 B 3 GB of vocabulary, excluding the index.
Candidate shortlists 300 million nodes × 10 references × 8 B 24 GB before transitions, strings and overhead.
Illustrative full snapshot Measured extrapolation: 40 GB Validate with a representative build.
Old and new versions together 2 × 40 GB 80 GB during replacement, before process reserve.
Response traffic 231,481/s × 500 B About 116 MB/s average.

A broad prefix may match millions of words. Traversing its few characters is cheap; enumerating and sorting every match is not. Extra replicas repeat that expensive work. Precomputing a bounded shortlist at each prefix trades memory and update effort for predictable serving cost.

The node count and snapshot size are assumptions to benchmark, not deductions from vocabulary bytes. Compression can reduce chains of single-child nodes, and compact arrays can reduce pointer overhead. Plan peak loading memory as well as steady-state memory: a machine that holds one snapshot may fail when asked to stage its replacement.

06Tie the response to input and index versions

The request identifies the typed prefix, locale and current browser input sequence. The response returns that sequence, the normalized prefix, the index version and a bounded list of term IDs with display text.

Suggestion request

GET /v1/suggest?prefix=ca&locale=en-US&limit=10&requestSeq=12

Illustrative response for the five running terms

{
  "requestSeq": 12,
  "normalizedPrefix": "ca",
  "indexVersion": "snapshot-42",
  "terms": [
    {"termId": "t-cat", "displayText": "cat"},
    {"termId": "t-capital", "displayText": "capital"},
    {"termId": "t-captain", "displayText": "captain"},
    {"termId": "t-caption", "displayText": "caption"},
    {"termId": "t-cap", "displayText": "cap"}
  ]
}

The example IDs identify the five terms; internal scores need not be exposed.

Interface Purpose
Suggest request Retrieve bounded candidates for one prefix and locale.
Submitted-search event Record the selected or submitted term with a stable event identity.
Delete private history Remove user-scoped history under its retention policy.
Snapshot manifest Name the immutable index, normalization version, ranges and checksums.

Do not count every typed prefix as a successful search. Otherwise “cap” accumulates popularity merely because people continue typing “capital.” Choose submitted searches as this design’s score input, deduplicate repeated event IDs over the processing window and bound contributions from abusive clients.

Return fewer than ten results when matching or approved candidates are insufficient. An empty suggestion list is a valid degradation, while the search box remains usable. Render display text safely rather than interpreting it as markup. The browser discards a response for an old input even if the server used the newest index; input freshness and ranking freshness solve different problems.

Request traceA later response can belong to older input

The browser renders only a response matching its latest input sequence.

A later response can belong to older inputThe browser renders only a response matching its latest input sequence. browser to api: 12: ca; browser to api: 13: cap; api to browser: 13 returns; render cap results; api to browser: 12 returns; discard old inputPARTICIPANTBrowserPARTICIPANTSuggestion API1. 12: ca2. 13: cap3. 13 returns; render cap results4. 12 returns; discard old inputsync
Read each connection in order
  1. sync12: caBrowser → Suggestion API
  2. sync13: capBrowser → Suggestion API
  3. sync13 returns; render cap resultsSuggestion API → Browser
  4. sync12 returns; discard old inputSuggestion API → Browser

07Store enough information to rebuild the shortlist

Durable records supply the vocabulary and popularity evidence used to generate the serving trie.

Record Fields or stored information Purpose
Term termId, normalizedText, displayText, locale Keeps one durable identity and both normalized and display forms of a term.
Time-bucketed counts Submitted-search counts grouped into time buckets Supplies the retained popularity evidence used by builds.
Approved snapshot metadata Immutable index identity, normalization version, ranges and checksums Identifies a validated serving artifact.

The serving trie must not be the only copy of vocabulary or popularity evidence.

Each node stores transitions, an optional terminal term and a bounded candidate list containing term IDs and scores. Store display strings once in a term table rather than repeating full strings at every prefix. A compressed edge can consume several characters; when a query ends inside a matching compressed edge, its candidates still come from that subtree.

Choose a ten-day sliding window of submitted-search counts, aggregated into hourly buckets. A score is the sum of the included buckets under the build’s fixed cutoff. Older buckets expire, so scores can decrease. This is different from exponential decay, which gradually changes older events’ weights; do not mix those definitions in one unexplained formula.

Public cache keys include normalized prefix, locale and index version. Private history is stored separately by authenticated user. If personalization is added, merge a bounded personal candidate source after retrieving public candidates, then apply the same policy checks. Never put the resulting private response into a cache shared by other users.

08Precompute winners without forgetting excluded terms

Build each prefix’s top ten from its own complete term and its child prefixes’ top tens. Why is that enough? A term excluded from a child’s top ten already has ten better matches. Those same matches qualify for the shorter parent prefix, so the excluded term cannot win there either. This requires the same global score and tie-breaker everywhere.

The serving shortlist alone is insufficient for future updates. Consider k = 2 under cap:

Term Original score Score after older counts expire
capital 700 50
captain 500 500
caption 400 400
cap 100 100

The original shortlist is capital, captain. After the decrease, the correct answer is captain, caption. Lowering capital’s score inside the old pair would never discover caption.

Therefore build from complete retained terms and counts, recomputing child and parent lists bottom-up. Building separately avoids changing scores and prefix shortlists while queries read them. The online request then traverses the prefix and reads a bounded prepared list instead of sorting a huge subtree.

The top-k argument assumes global independent scores. Arbitrary personalization or diversity rules can change which excluded terms become useful. That extension needs a larger candidate pool or separate personal candidates and an explicit recall tradeoff.

09Publish coherent snapshots and route prefixes

Queries reuse precomputed rankings while popularity changes accumulate. Build a separate read-only index hourly from a fixed vocabulary/count cutoff. Check representative prefixes, checksums, ranking and memory size before directing queries to it. Serving requests retain one index version until they finish; new requests can switch to the replacement while older readers finish on the previous version.

If both versions do not fit on one machine, stage new hosts and shift traffic after readiness checks. Do not solve a loading-memory shortage by freeing data still used by active requests. Keep the previous artifact for rollback and retain durable inputs for rebuilding.

Partition large indexes by measured lexical ranges, meaning contiguous portions of the normalized term ordering. A prefix can intersect more than one range, so a router asks the relevant shards and merges their bounded candidates. Every shard response uses the request’s pinned snapshot version. Hashing complete terms spreads storage but scatters prefix matches, usually requiring every shard or a separate prefix-routing index.

Equal first-letter shards are rarely balanced. Split large ranges and replicate hot ranges according to measured memory and traffic. Splitting unrelated data does not reduce requests for the exact same hot prefix; replicate its serving work and cache its public candidates. Keep candidate limits and shard fanout bounded so a broad query cannot overwhelm the cluster.

Store the source vocabulary, count buckets and published snapshot metadata with durable majority commits across three replicas in independent regional failure domains. That meets the chosen one-node-loss boundary only if failover preserves acknowledged inputs. Snapshot-serving replicas remain replaceable; keeping ordinary search available is the fallback when suggestions cannot be served safely.

10Filter removals after every candidate source

Hourly ranking freshness is acceptable here, but a removed term should not survive simply because it remains in an old snapshot. Before returning suggestions, the API checks the final candidate IDs against current policy state. This includes candidates from caches and private history. If the policy check is unavailable, return no suggestions rather than assume an old allowed decision remains valid.

Choose this request-time check for the worked design. Its extra dependency and latency must be budgeted; a separate replicated policy service can batch the small list of candidate IDs. Faster distributed policy caches with bounded freshness are an Advanced alternative, because an explicit removal deadline also needs a clock, expiry and client-display contract.

Filtering can leave a short list. Precompute a modest larger pool if measurements show that approved results are frequently lost, but do not perform an unbounded subtree scan or refill afterward from an unfiltered source. Increasing the pool costs memory at every retained prefix.

Private history adds another boundary: logout and history deletion must clear user-scoped cached data. Public popularity counts should not expose raw personal search logs. Prefixes can contain secrets or pasted identifiers, so minimize logging and restrict retained raw data.

Design diagramBuild snapshots offline; merge and filter suggestions online

The builder reads a fixed input cutoff and publishes validated immutable indexes. The API pins one version, checks a public candidate cache or queries the relevant lexical-range replicas, and merges their results. Current policy checks run after every candidate source before suggestions return to the search box.

Build snapshots offline; merge and filter suggestions onlineThe builder reads a fixed input cutoff and publishes validated immutable indexes. The API pins one version, checks a public candidate cache or queries the relevant lexical-range replicas, and merges their results. Current policy checks run after every candidate source before suggestions return to the search box. user to api: Prefix, locale and input sequence; api to cache: Versioned public candidates; api to index: Relevant ranges; pinned version; api to policy: Filter final candidate IDs; builder to source: Read fixed vocabulary/count cutoff; builder to index: Publish validated snapshotsPrefix, locale and inputsequenceVersioned public candidatesRelevant ranges; pinnedversionFilter final candidate IDsRead fixed vocabulary/countcutoffPublish validated snapshotsACTORSearch boxSERVICESuggestion API +range routerSTORELexical-range indexreplicasCACHEPublic candidatecacheSERVICECurrent term-policyserviceSERVICEHourly snapshotbuilderSTOREVocabulary + hourlycountssyncasync
Read each connection in order
  1. syncPrefix, locale and input sequenceSearch box → Suggestion API + range router
  2. syncVersioned public candidatesSuggestion API + range router → Public candidate cache
  3. syncRelevant ranges; pinned versionSuggestion API + range router → Lexical-range index replicas
  4. syncFilter final candidate IDsSuggestion API + range router → Current term-policy service
  5. syncRead fixed vocabulary/count cutoffHourly snapshot builder → Vocabulary + hourly counts
  6. asyncPublish validated snapshotsHourly snapshot builder → Lexical-range index replicas

11Keep search usable while suggestions recover

If a snapshot build fails, continue serving the previous validated index with current policy checks. If loading exceeds memory or fails its checksum, that replica remains unready. A pointer to a partially loaded artifact must not reach request routing.

Popularity aggregation must tolerate repeated events. One simple implementation recomputes each completed hourly bucket from a fixed input-log range, deduplicating event IDs before publishing that bucket. Replaying the build produces the same counts instead of incrementing them again. Event retention bounds how far back reconstruction is possible.

When a hot-prefix replica fails, spare replicas and shared public candidate caches absorb traffic within measured capacity. Admission limits preserve service health. Suggestion failure should leave normal query submission available; it should not freeze the input box waiting for retries.

Monitor end-to-end latency, empty-result rate, response-discard rate, snapshot age, load-time peak memory and policy-check failures. Evaluate relevance on held-out prefixes as well as clicks, since click volume can reward sensational suggestions. Apply contribution limits and detect sudden suspicious popularity spikes before they dominate the next build.

12Check the design against the requirements

Use the agreed lists to check the finished design. The tests below still need to establish the targets; a proposed mechanism is not a measured result. FR refers to the numbered functional requirements above; NFR refers to the numbered non-functional requirements.

Requirement Design mechanism Validation and remaining limit
FR1–2 + NFR2: useful current-input suggestions Prefix traversal, prepared candidates, compatible normalization and browser request sequences. Test ca/cap response reordering and benchmark p95 after-send latency including network and final policy checks.
FR3 + NFR3–4: coherent popularity updates Complete retained inputs and validated immutable hourly snapshots. Lower a top term’s score, fail a build and stage two versions; confirm correct replacement and bounded memory.
FR3–4 + NFR5: safe degraded results Final current-policy filtering and separately scoped private history. Block a cached term and interrupt policy checks; suggestions may become empty while ordinary search remains usable.
NFR1,6: bounded peak work Lexical-range ownership, hot-range replicas and bounded public candidate caching. Load-test hot prefixes and broad prefixes. Splitting unrelated terms does not by itself remove one hot-prefix bottleneck.
NFR4: recoverable inputs Durably replicated vocabulary/count records and retained snapshot artifacts. Fail a source node and rebuild an index; an in-memory trie alone cannot meet the durability requirement.

13Rapid revision

Remember: Precompute rankings to reuse them across queries; replace the whole snapshot so readers never see half-updated shortlists.

Decision Problem it solves Accepted cost or limit
Prefix trie with terminal markers Find terms that start with the typed prefix. No typo or semantic matching by itself.
Precomputed candidate lists Avoid broad subtree scans per query. Memory and rebuild work.
Hourly immutable snapshots Read one complete ranking without update locks. Popularity can lag.
Keep all terms and count history Replace winners when their scores decrease. Store the full vocabulary and time-bucketed counts.
Alphabetical partitions with replicas Route matching data and serve hot prefixes. Merge bounded shortlists when a prefix spans shards.
Check the blocklist before returning suggestions Filter blocked public and personalized suggestions. Return none if the blocklist check fails.
Browser request sequence Prevent old responses replacing newer input. Cancellation alone remains insufficient.

Close by following ca: the browser numbers its input, the server reads one index version and checks its candidate list, then the browser displays the response only if ca is still current. Explain that serving speed came from moving work into index construction, not from ignoring ranking correctness. The next measurements are peak snapshot memory, hot-prefix load and useful suggestion quality. Personalization, typo correction and hard removal deadlines can then be discussed as specific extensions with additional requirements.

Practise the interview questions

Say your answer aloud before opening the model answer. Then answer the follow-up and compare the reasoning.

Foundation · Question 1

How does a trie answer a prefix query?

Reveal a model answer

Follow character edges to the prefix node, then use descendant terminal terms. A terminal marker distinguishes a complete term from an intermediate path.

What the answer must demonstrate: Explains prefix traversal and terminal semantics.

Applied · Question 2

Why is prefix-length traversal not the whole query cost?

Reveal a model answer

A broad prefix can have millions of matching descendants. Enumerating and sorting them dominates the few edge traversals.

What the answer must demonstrate: Counts descendant enumeration, not just edge traversal.

Applied · Question 3

What goes wrong when a top-ranked term loses popularity?

Reveal a model answer

The old shortlist does not contain the best excluded replacement. Retained vocabulary and counts are needed to recompute winners.

What the answer must demonstrate: Finds the missing replacement candidate after a decrease.

Applied · Question 4

Why build immutable snapshots?

Reveal a model answer

Build the replacement separately so a query never combines a changed term score with an old prefix shortlist. Each request uses one complete version of the terms, normalization rules and scores.

What the answer must demonstrate: States coherent data and simultaneous-version memory costs.

Applied · Question 5

Why does request cancellation not prevent stale display?

Reveal a model answer

The old response may already be completed or in flight. The browser must compare its input sequence before rendering.

What the answer must demonstrate: Uses an explicit latest-input comparison.

Applied · Question 6

Why prefer lexical ranges to hashing complete terms here?

Reveal a model answer

Prefix matches occupy related lexical ranges, allowing targeted routing. Hashing terms scatters matches and usually requires broad fanout.

What the answer must demonstrate: Connects partition layout to query locality and hot-key replication.

Applied · Question 7

Where should blocked-term filtering run?

Reveal a model answer

Combine public index/cache candidates with the authenticated user’s private-history candidates, then check the whole list before returning it.

What the answer must demonstrate: Checks the combined candidate list and does not refill it from an unchecked source.

Follow-up · Question 8

Why is a personalized result unsafe in a public cache?

Reveal a model answer

It may reveal one user’s history to another. Share only public candidates and perform authenticated personal merging separately.

What the answer must demonstrate: Keeps personal data out of shared results and recognizes candidate recall limits.

Blank-page exercise · 45 minutes

Build the answer yourself

Design exact-prefix autocomplete with hourly popularity updates. Explain a score decrease, an old browser response and a blocked term in a cached candidate list.

  • 0–5 min: agree numbered functional and non-functional requirements for prefix matching, ranking, 200 ms latency, hourly freshness, current approval and privacy.
  • 5–12 min: demonstrate the five-word trie and workload estimates.
  • 12–20 min: define events, normalized terms and snapshot records.
  • 20–30 min: precompute top candidates and explain sharding/rollout memory.
  • 30–38 min: handle score decreases, final policy filtering and input sequences.
  • 38–45 min: review the final design against the numbered FR/NFR lists, check latency/memory/freshness and blocked-term tests, and state typo/personalization exclusions.

Check that each component and design decision follows from your requirements and workload.

Recall the key ideas

Answer from memory before opening each card. Explain why the choice works and what it costs. Revisit missed cards tomorrow.

Design a typeahead servicePopularity changes while queries read ca. Why build a separate hourly snapshot instead of editing the live shortlists?Recall first, then reveal

A live update can change a term’s score before its prefix shortlist changes. A separate build lets each query use one complete ranking; the cost is delayed popularity updates and memory for both versions.

Prepare once, read consistently.

Return to lesson
Design a typeahead serviceWhy retain terms outside the current top k suggestions?Recall first, then reveal

A lower-ranked term may enter the shortlist when a leading term’s score falls or the term is removed.

Today’s loser may win tomorrow.

Return to lesson
Design a typeahead serviceA response for ca arrives after the user types cap. Does a consistent index snapshot make it safe to display?Recall first, then reveal

No. The snapshot keeps ranking data consistent; the browser must also check the input sequence and discard the old ca response.

Version for data, sequence for typing.

Return to lesson

Final revision

Summary and interview notes

Precompute ranked prefix lists and serve each query from one complete snapshot. Filter prohibited suggestions before returning them; the browser displays a response only for its current input.

Remember these points

  • Precompute frequent prefix work.
  • Build from complete retained vocabulary and scores.
  • Pin a compatible snapshot for each request.
  • Filter every candidate source and discard old-input responses.

Interview tips

  • Work through cap with k equal to two.
  • Separate string bytes from index and rollout memory.

Important qualifications

  • Hard worldwide removal deadlines require more than an hourly snapshot.
  • Exact-prefix matching does not include typo correction.

Continue after the core interview

Explore the advanced version

The advanced lesson keeps the full detailed design. Use these sections when you want to examine the stronger requirements and failure cases.

Technical references

Practice marks stay in this browser.