System-design interview · Core interviews
Design a typeahead service
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 practiceUseful 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.
Dotted concept links open the relevant explanation in a new tab.
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.
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.
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.
Update popularity and policy. Ingest identified submitted-search events, rebuild popularity snapshots and remove blocked terms from final candidate results.
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.
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.
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.
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.
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.
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.
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.
The serving application reads a trie built from durable approved terms and counts.
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.
The browser renders only a response matching its latest input sequence.
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.
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.
Read each connection in order
- syncPrefix, locale and input sequenceSearch box → Suggestion API + range router
- syncVersioned public candidatesSuggestion API + range router → Public candidate cache
- syncRelevant ranges; pinned versionSuggestion API + range router → Lexical-range index replicas
- syncFilter final candidate IDsSuggestion API + range router → Current term-policy service
- syncRead fixed vocabulary/count cutoffHourly snapshot builder → Vocabulary + hourly counts
- 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.
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.
Interviewer follow-up
Why is cap both a result and an internal node?
Reveal the follow-up answer
It is a complete term and a prefix of capital, captain and caption.
What the answer must demonstrate: Explains prefix traversal and terminal semantics.
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.
Interviewer follow-up
What changes the online cost?
Reveal the follow-up answer
Precompute a bounded ranked list at each prefix, paying memory and offline build work.
What the answer must demonstrate: Counts descendant enumeration, not just edge traversal.
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.
Interviewer follow-up
Give the cap example.
Reveal the follow-up answer
When capital drops below caption, captain/caption becomes the correct top two even though caption was absent from the old pair.
What the answer must demonstrate: Finds the missing replacement candidate after a decrease.
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.
Interviewer follow-up
What memory trap appears during rollout?
Reveal the follow-up answer
Both the current and replacement indexes may need to coexist, plus process reserve.
What the answer must demonstrate: States coherent data and simultaneous-version memory costs.
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.
Interviewer follow-up
Does a newer index version fix an old-prefix response?
Reveal the follow-up answer
No. Index freshness and association with current typed input are independent.
What the answer must demonstrate: Uses an explicit latest-input comparison.
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.
Interviewer follow-up
Does splitting ranges fix one hot prefix?
Reveal the follow-up answer
Not by itself. Repeated identical queries need replicas or cached candidates.
What the answer must demonstrate: Connects partition layout to query locality and hot-key replication.
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.
Interviewer follow-up
What if filtering leaves only six candidates?
Reveal the follow-up answer
Return six or use a bounded larger prefiltered pool; do not refill from an unchecked source.
What the answer must demonstrate: Checks the combined candidate list and does not refill it from an unchecked source.
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.
Interviewer follow-up
Does the public global top ten guarantee the best personal term?
Reveal the follow-up answer
No. A separate personal candidate source or larger pool is needed for that recall.
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 lessonDesign 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 lessonDesign 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 lessonFinal 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.
- Hard removal deadlines across servers and clients
Add freshness expiry, uncertainty allowance and display rules when a timed guarantee is required.
- Reader lifetime and cross-shard snapshot replacement
Analyze pointer lifetime and version-coherent routing during rollout.
- Personal reranking and alternative completion indexes
Measure additional candidate recall and implementation-specific memory/update tradeoffs.
- Sampling and aggregation recovery
Study event sampling limits and stronger replay/cutoff protocols.
Technical references
- Elasticsearch completion suggesterOfficial completion API and operational considerations; one possible implementation of fast prefix suggestions.
- Unicode normalization formsDefines normalization behavior needed for consistent text matching.
Practice marks stay in this browser.