Hiring BarSupport

Design Search Autocomplete (Typeahead) — Staff-Level Case Study

Case study74 min read8 diagrams

Technologies referenced in this case study: Redis · Elasticsearch · Kafka · Flink

Related case studies: Search Indexing · CDN & Edge Caching · Distributed Caching · Leaderboards & Counting · Stream Processing

How to Use This Case Study#

Organized for interview use first, reference second. Full-text retrieval, inverted indexes and relevance scoring live in Search Indexing; this case study is about answering a prefix in under 100ms, millions of times a second, without ever suggesting something you'll regret.

ModeTimeWhat to Read
Quick Review15 minExecutive Summary → Interview Walkthrough → Fault Lines table → Drills 1–3
Targeted Study1–2 hrsExecutive Summary → Walkthrough → Section 3 (Fault Lines) → Section 4 (Failures) → Deep Dives 1, 2 and 4
Deep Dive3+ hrsEverything, including the Principal Lens (Section 11) and the appendices
What is Search Autocomplete? — Why interviewers pick this topic

Typeahead returns a short ranked list of completions as the user types: "how to" → "how to tie a tie", "how to screenshot on mac", … It fires on nearly every keystroke, so it is usually the highest-QPS read endpoint a search product owns — often 5–10× the QPS of search itself — and it is the most visible: suggestions appear before the user has asked for anything.

Before vs After — the offensive suggestion incident:

Without a safety design:
t=0:      A coordinated group searches "<public figure> is a <slur>" 40K times
t=+1h:    Hourly pipeline promotes it into the top-5 for "<public figure> is"
t=+1h:    Prefix tables rebuilt and pushed to all serving nodes
t=+3h:    Screenshot trends on social media
t=+4h:    Engineer manually edits the blocklist file; full rebuild needed
t=+6h:    Rebuild + rollout completes. Press coverage already written.

With a safety design:
t=0:      Same coordinated burst
t=+1h:    Pipeline requires ≥N distinct users over ≥M days; burst fails threshold
t=+1h:    Velocity anomaly flags the pattern for trust-and-safety review
t=+20min: (If anything slipped through) T&S adds a serving-time override
t=+21min: Override propagates to all nodes and edge caches purge — no rebuild

Why interviewers reach for this question: the textbook answer (a trie with top-K at each node) fits on a whiteboard in five minutes. Everything that decides your level comes after that: the per-keystroke latency budget, where caching happens, how fresh suggestions get, who owns the blocklist, and how to suggest in Japanese where users type phonetic input that isn't the final text.

Mechanics Refresher: Data Structures
StructureHow It WorksProsCons
Trie, walk + DFS at query timeWalk to prefix node, traverse subtree to find top-KSimple; supports any KSubtree traversal for short prefixes touches millions of nodes — unbounded latency
Trie with top-K cached per nodeEach node stores its precomputed top-K completionsO(prefix length) lookupPointer-heavy memory (~10× the raw strings); rebuild to update rankings
Prefix table (hash map prefix → top-K)Precompute top-K for every prefix up to length L; store in KVO(1) lookup; trivially shardable and cacheable; any KV store worksStorage grows with Σ(query length); long-tail prefixes need a fallback
FST (finite state transducer)Compressed automaton sharing prefixes and suffixes; weights on arcs5–10× smaller than a trie; Lucene / Elasticsearch completion suggester use itImmutable — rebuild to change; harder to update incrementally
Search index with edge n-gramsIndex every prefix of every term in an inverted indexHandles mid-string and multi-term matching10–100× slower per request than a lookup; overkill for query completion

For most production systems: precomputed top-K per prefix, stored as an immutable, versioned prefix table (or FST) held in memory, with a small real-time overlay for trending and a serving-time override layer for safety. The data structure is never the interview question.


Executive Summary

If you only read one section, read this. Typeahead is a read-optimized precomputation problem with a safety obligation attached.

What This Interview Actually Tests#

Typeahead is not a trie question. Everyone can draw a trie.

This is a latency-budget and precomputation question that tests:

  • Whether you derive the design from a per-keystroke budget (~100ms end to end, ~10–20ms server)
  • Whether you move work from query time to build time — and know what that costs in freshness
  • Whether you place caching at every layer (client, edge, server) and know which layer absorbs what share of traffic
  • Whether you treat suggestions as published content with safety, legal and privacy obligations

The key insight: every suggestion is something your product said to the user before they asked. Staff engineers design typeahead as a publishing pipeline — build offline, review, publish versioned, override instantly — with a serving tier that is just a fast lookup.

The L5 → L6 → L7 Contrast — Start Here#

BehaviorSenior (L5)Staff (L6)Principal (L7)
First moveDraws a trie with top-K per nodeAsks "query completion, entity lookup, or in-app navigation?" and derives a per-keystroke latency budgetAsks which surfaces already run their own suggesters (search box, maps, shopping, help center) and who owns suggestion safety across them
Data structureTrie in memory, updated on every searchPrecomputed top-K per prefix, immutable versioned snapshot, built offline; tiny real-time overlay for trendingTreats the suggestion index as a shared artifact with a build/publish contract; serving is commodity
Freshness"Update the trie in real time"Daily/hourly batch for the base, 5-minute streaming overlay for trending, with velocity-anomaly gatingSets freshness tiers by surface (news: minutes, commerce: hours) and prices each tier against the abuse surface it opens
Safety"Filter bad words"Blocklists + k-anonymity thresholds at build time; serving-time override with minutes-level propagation; T&S owns policyWrites the org's suggestion policy with legal and T&S; defines removal SLA, appeals, and audit; one override service for every surface
Scale"Shard the trie by first letter"Replicate the whole index if it fits (tens of GB); edge-cache short prefixes; shard only the long tailDecides what lives on the client, the CDN and the origin based on cost per million keystrokes
Why "first move" separates levels

L5: Starts from the data structure because that's what the prompt names. The result is a design with no stated latency budget and no notion of who the suggestions are for.

L6: "Query completion for a web-scale search box, entity typeahead for people and pages, and command completion inside an app are three different systems. The first is popularity-ranked and cacheable; the second is personalized and graph-ranked; the third is small enough to ship to the client. I'll design query completion and show where personalization plugs in. Budget: 100ms keystroke-to-render, so 10–20ms server p99."

L7: "How many suggesters does the company run today? If search, maps and shopping each have one, they each have their own blocklist — and the next safety incident will come from the one with the weakest list. I'd design the shared build-and-safety pipeline and let each surface own ranking."

Why "freshness" separates levels

L5: Proposes updating counts in the trie on every search. That puts writes on a structure serving a million reads a second and — more importantly — lets any burst of searches publish itself immediately.

L6: Splits freshness into tiers. The base index rebuilds daily (or hourly) from aggregated logs with time decay. A streaming job detects trending queries in 5-minute windows and publishes a small overlay, gated by distinct-user thresholds and velocity checks. Serving merges base + overlay. Fresh enough for breaking news, not so fresh that a brigade can publish itself.

L7: Recognizes that freshness is abuse surface. Every minute shaved off trending latency is a minute less for review. Sets freshness per surface with T&S: news may get 5-minute trending with human-in-the-loop for sensitive entities; product search gets hourly with no human review.

Why "safety" separates levels

L5: A profanity list applied at build time.

L6: Four layers: (1) privacy thresholds — a query must be issued by ≥N distinct users to be suggestible, so private searches never leak; (2) policy classifiers and blocklists at build time; (3) a serving-time override (deny by exact string, by prefix+completion pair, by entity) that propagates in minutes without a rebuild; (4) edge-cache purge for affected prefixes. Trust & Safety owns the policy; search infra owns the mechanism and the propagation SLA.

L7: Turns it into governance: a published suggestion policy, a legal-removal workflow with an SLA, an audit log of every override, and a shared override service for all surfaces. Measures "time to removal" as an org SLO.

The Staff Positions#

PositionRationale
Precompute top-K per prefix offlineMoves all ranking work out of the 10–20ms serving budget; serving becomes a lookup
Immutable, versioned index snapshotsAtomic swap, instant rollback, reproducible debugging; no in-place mutation at 1M QPS
Base + trending overlay + override layerThree update speeds for three needs: quality (daily), freshness (minutes), safety (seconds)
Cache at the client and the edge first50–70% of keystroke requests never need to reach origin
Replicate before you shardA filtered prefix table is tens of GB — it fits in RAM; sharding adds fan-out and hot-shard problems for little benefit
k-anonymity threshold before anything is suggestibleSuggestions are published content; a query seen from 3 users is someone's private search
Personalization as a re-rank, not a separate indexKeep the global index cacheable; blend a small per-user candidate set at serve time

The Three Intents#

IntentConstraintStrategyFailure ModeCorrectness Bar
Query completion (web/product search)Massive QPS, global popularity, cacheablePrecomputed top-K per prefix; edge-cached; trending overlayOffensive or stale suggestions; latency spikesRelevance measured by suggestion CTR and keystrokes saved; zero policy-violating suggestions
Entity typeahead (people, pages, places)Personalized, graph-dependent, privacy-sensitivePer-user candidate set (friends, recent) + global entity index; merge and rankLeaking private entities; missing the obvious friendRecall of the intended entity in top-3
In-app navigation / command completionSmall corpus (10K–1M items), per-tenant permissionsShip index to client or query per-tenant index; fuzzy matchingShowing items user can't accessPermission-correct; typo tolerant

🎯 Staff Move: "I'll design query completion for the main search box — that's where the QPS, the caching and the safety risk live. Entity typeahead reuses the serving tier with a per-user candidate set merged in, and I'll show where that plugs in."

The Five Fault Lines#

#Fault LineThe Tension
1Precompute vs Compute at Query TimeLookup in 1ms with fixed rankings, or rank on demand with flexibility and 10–50× the cost?
2Freshness vs Safety & StabilitySurface trending queries in minutes, or gate them for abuse and quality?
3Global Cacheable vs PersonalizedOne answer per prefix (cache everywhere) or per-user answers (cache nowhere)?
4Replicate vs ShardFull copy on every node (simple, no fan-out) or partition by prefix (scales memory, hot shards)?
5Central Policy vs Product AutonomyOne suggestion policy and override service, or each surface curates its own?

In the Wild: Real Production Systems#

Why this section belongs here: Typeahead is a well-documented problem with public engineering write-ups. Citing them shows you know the hard parts are latency and safety, not tries.

Facebook — Typeahead for people and pages#

Facebook's engineering blog post "The life of a typeahead query" (2010) described a design that pre-loads a user's first-degree connections and frequently-used entities to the browser, so many queries are answered client-side before any request is sent. Server-side, queries fan out to a global index of entities and a per-user graph index, results are merged and ranked by social signals, all within a tight budget measured in tens of milliseconds.

Staff insight: The fastest request is the one you don't make. Pushing a per-user candidate set to the client is a caching decision driven by the latency budget — and it's the canonical example of personalization as a merge, not a separate global index.

LinkedIn — Cleo#

LinkedIn open-sourced Cleo (2012), a typeahead library used for its search boxes. It partitions a large entity set, uses compact per-element prefix signatures (Bloom-filter style) to cheaply reject non-matching candidates, and then scores the survivors — supporting both "network" (personal connections) and "generic" (global) typeahead.

Staff insight: Separating a cheap candidate filter from an expensive scorer is the same move as top-K precomputation: bound the work per keystroke so latency doesn't depend on corpus size.

Google — Autocomplete policies#

Google publishes autocomplete policies describing categories of predictions it removes (e.g., sexually explicit, hateful, violent, dangerous content, and certain predictions about individuals), and gives users a way to report predictions. Removals are driven by automated systems plus enforcement teams.

Staff insight: At scale, suggestion safety is a published policy with an enforcement pipeline, not a word list. In an interview, naming the policy owner and the removal path is a stronger signal than any data structure.

Elasticsearch — Completion suggester#

Elasticsearch's completion suggester builds an in-memory FST per segment for prefix lookups, trading index-time cost and immutability for very fast reads. It supports weights and contexts (e.g., category or geo filters) but not arbitrary ranking at query time.

Staff insight: Even a general-purpose search engine ships a precomputed, immutable structure for autocomplete — because query-time full-text search can't meet the per-keystroke budget.

What Interviewers Probe#

After You Say...They Will Ask...(What They're Evaluating)
"Trie with top-K at each node""How much memory? How do you update rankings without locking a structure serving 1M QPS?"Do you know the build/serve split?
"We'll update counts on every search""A brigade searches an offensive phrase 50K times. What happens?"Freshness as abuse surface
"Cache in Redis""What's the hit rate for 'a'? For 'how to tie a bow ti'? Where's the cache closest to the user?"Layered caching and the prefix-length distribution
"Shard by first letter""What's the QPS on the 's' shard vs the 'x' shard?"Hot shards; replicate-vs-shard judgment
"Personalize by user history""Now nothing is cacheable. What's your latency and cost?"Personalization as re-rank
"Filter bad words""Legal sends a court order to remove a suggestion. How long until it's gone everywhere?"Override layer and propagation SLA

System Architecture Overview#

Diagram: System Architecture Overview

Reading the diagram: The client and edge absorb most keystrokes. The serving tier is a dumb, fast lookup over an in-memory snapshot plus a small trending overlay, filtered through an override list on every response. All ranking, privacy filtering and policy classification happen offline in the build pipeline. Trust & Safety has a direct path to the override filter and the CDN purge — removals never wait for a rebuild.

Quick-Reference: The 30-Second Cheat Sheet#

TopicThe L5 AnswerThe L6 Answer — Say ThisThe L7 Answer — Say This
Data structure"Trie with top-K per node""Precomputed top-K per prefix, immutable snapshot, atomic swap. The structure is an implementation detail.""The index is a published artifact with a build contract; any surface can serve it."
Latency"It's in memory, so it's fast""100ms keystroke-to-render; 10–20ms server p99; debounce 50–100ms; client and edge serve most of it.""We pay per keystroke — I'd price client vs edge vs origin per million keystrokes."
Freshness"Update on every search""Daily base, hourly refresh, 5-min trending overlay gated by distinct users and velocity.""Freshness is abuse surface; T&S sets the trending tier per surface."
Personalization"Per-user trie""Global top-K plus a per-user candidate set merged at serve time; keeps the global response cacheable.""Personalization is opt-in per surface and has a privacy review — it changes what we store about users."
Safety"Profanity filter""k-anonymity threshold, policy classifiers, serving-time overrides in minutes, edge purge.""One override service, one policy, one audit log for every suggester; time-to-removal is an SLO."
Scale"Shard by first letter""Replicate the full index — it's tens of GB. Shard only if it doesn't fit, by hash, with short prefixes replicated everywhere.""Cost lives at the edge, not the index; the index is cheap, the keystrokes aren't."

Key Numbers Worth Memorizing#

MetricValueWhy It Matters
Keystroke-to-render budget~100msAbove ~100–150ms, suggestions feel laggy and lag behind typing
Inter-keystroke interval~100–300msDebounce at 50–100ms drops ~30–50% of requests without perceived lag
Server p99 budget10–20msWhat's left after RTT (20–80ms) and rendering
Suggest requests per search~4–8 after debounceTypeahead QPS is several times search QPS
Example scale5B searches/day → ~30B suggest req/day → ~350K/s avg, ~1M/s peakDrives every caching decision
Requests with prefix length ≤ 3~30–40%Short prefixes are few and hot — perfect for the edge
Client + edge hit rate~50–70% combinedOrigin sees a fraction of keystrokes
Suggestible queries after filtering~10–100MMin-count + k-anonymity cut billions of raw queries to this
Prefix table size (top-10, L ≤ 20)~20–80 GBFits in RAM on a large node — replicate rather than shard
k-anonymity threshold≥ 50–1,000 distinct users over ≥ 7–30 daysPrevents private queries from becoming suggestions
Trending overlay latency5–15 minBreaking news needs minutes; faster increases brigade risk
Override propagation SLA< 5–15 min to all nodes + edgeThe real safety requirement

Interview Walkthrough

The most common mistake: Candidates spend 20 minutes on trie node layouts and memory per pointer, then run out of time before latency budgets, caching layers, freshness and safety — which is where the level is decided. The phases below get the data structure done in 5 minutes.


Phase 1: Requirements & Framing (2–3 min)#

Functional requirements in 30 seconds:

"As the user types into the search box, return up to 10 ranked completions for the current prefix, per keystroke."

Then the framing that matters:

"There are three typeahead problems hiding here — query completion, entity lookup for people and pages, and in-app command completion — and they rank and cache differently. I'll design query completion for the main search box and show where personalization plugs in."

Then commit to numbers:

"I'll assume 5B searches a day, ~6 suggest requests per search after debouncing — about 350K requests/s average and 1M/s peak. Budget: 100ms from keystroke to rendered list, so the server gets 10–20ms p99. Suggestions must never include policy-violating or private queries, and removals must propagate within minutes."

🎯 Staff Move: Putting "removals must propagate within minutes" in the requirements tells the interviewer you see suggestions as published content. It also sets up the override layer, which is the piece most candidates never design.


Phase 2: Core Entities & API (1–2 min)#

  • Suggestion: text, score, source (base / trending / personal), entity_ref?
  • PrefixEntry: (locale, normalized_prefix) → [top-K suggestion ids + scores]
  • IndexSnapshot: version, locale, built_at, policy_version, checksum
  • Override: match (exact / prefix+completion / entity / regex), action (deny / demote), reason, owner, expires_at

Serve path:

GET /v1/suggest?q=how+to+t&locale=en-US&client=web&session=…
200 OK  Cache-Control: public, max-age=300   (if non-personalized)
{ "q": "how to t", "suggestions": [ {"text":"how to tie a tie","src":"base"}, … ],
  "index_version": "en-US-2026-10-01T03", "personalized": false }

Override path (T&S, cold):

POST /v1/overrides { match: {type:"pair", prefix:"<name> is", completion:"<term>"},
                     action:"deny", reason:"policy:harassment", ticket:"TNS-4411" }

🎯 Staff Move: "The response says whether it's personalized. Non-personalized responses are public-cacheable at the edge; personalized ones are private. That single bit decides whether a keystroke costs us a CDN hit or an origin request."


Phase 3: High-Level Architecture (≤5 min)#

Diagram: Phase 3: High-Level Architecture (≤5 min)

"Reads are a lookup in an in-memory prefix table — one hash get per request. All ranking happens in the offline build. Trending is a small overlay merged at serve time. Overrides are applied to every response. Client and edge caches take most of the traffic."


Phase 4: Transition to Depth#

"The lookup is the easy part. The design lives in four places: the per-keystroke latency budget and caching layers, how fresh suggestions get without letting brigades publish themselves, how personalization coexists with caching, and the safety pipeline. I'll start with the latency budget because it drives everything else."


Phase 5: Deep Dives (25–30 min)#

Deep DiveThe 60-Second VersionGo Here
Latency budget & caching100ms total: RTT 20–80ms, server 10–20ms, render 10ms. Debounce 50–100ms. Client caches prefix results; edge caches non-personalized prefixes with 5–15 min TTL. ~50–70% never reach origin.Fault Line 3, Appendix D
Index buildLogs → normalize → aggregate with exponential decay (half-life ~7 days) → k-anonymity filter → policy filter → top-K per prefix (L ≤ 20) → versioned snapshot → staged rollout.Fault Line 1, Appendix A
Freshness & trending5-min streaming windows; trending score = current rate vs baseline; require distinct-user and velocity checks; overlay of ≤100K entries merged at serve.Fault Line 2, Drill 4
PersonalizationPer-user recent queries (last ~100) in a KV store; serve-time merge with global top-K; personalized responses not edge-cacheable.Fault Line 3, Drill 6
Sharding & hot prefixesReplicate full snapshot (tens of GB) on every node; if too large, hash-shard long prefixes and replicate short ones everywhere.Fault Line 4, Section 4.2
SafetyBuild-time: thresholds, classifiers, blocklists. Serve-time: override list, minutes to propagate, edge purge. Audit everything.Fault Line 5, Deep Dive 2

Phase 6: Wrap-Up (2–3 min)#

"To summarize: a read-only, in-memory prefix table built offline, served from replicated nodes behind client and edge caches, with a trending overlay for freshness and a serving-time override layer for safety. The server budget is 10–20ms and most keystrokes never reach it. Next I'd add per-locale tokenization for CJK and IME input, typo-tolerant fallback for long prefixes that miss the table, and an experimentation framework for ranking. What I wouldn't build on day one is a real-time mutable trie — the freshness it buys isn't worth the safety and operational risk."


Common Timing Mistakes#

MistakeTime LostFix
Designing trie node memory layout5–10 minSay "precomputed top-K per prefix, FST or hash table" and move on
Debating Redis vs Memcached for the cache5 minThe important caches are the client and the CDN
Building a real-time update path into the trie10 minBatch base + trending overlay; explain why
Skipping safety until askedWhole levelPut propagation SLA in requirements
Ignoring non-Latin scriptsMissed L6 signalOne sentence on normalization and IME in the wrap-up

1. The Staff Lens#

1.1 Why This Problem Exists in Staff Interviews#

Typeahead is deceptively small. The functional spec is one sentence, and the textbook data structure takes five minutes. That leaves 40 minutes in which the interviewer learns whether you can reason from a latency budget, move work from read time to build time, cache at the right layers, and own something that is effectively a publishing system: every suggestion is a sentence your company says to millions of people, unprompted.

It also has an unusual shape: extreme read QPS (often the single highest-QPS endpoint in a search product), a tiny response, a mostly-static dataset, and a small but critical write path (overrides) that must propagate faster than anything else in the system.

1.2 The L5 → L6 → L7 Contrast — Visual#

Diagram: 1.2 The L5 → L6 → L7 Contrast — Visual

The L5 path produces a working autocomplete. The gap is that its first incident — a brigade, a court order, a hot prefix, a Japanese launch — has no designed answer.

1.3 The Staff Question That Cuts Through Everything#

"If a suggestion has to disappear right now, how long does it take, and who can make it happen?"

If the answer is "a rebuild, a few hours, an engineer", the design is L5. If the answer is "under 10 minutes to every node and every edge cache, by a T&S operator through an audited console", the design is Staff-grade. Answering it forces the build/serve split, the override layer, the cache purge path and the ownership model all at once.


2. Problem Framing & Intent#

2.1 The Three Intents — Explained#

Query completion. Completes what the user is typing into a full query, ranked by how often that query is issued (with time decay), how often the suggestion gets clicked, and freshness. The same prefix gets the same answer for most users in a locale — which makes it cacheable at the edge. The risk is reputational and legal: offensive, defamatory or private suggestions. The owner is search infra (mechanism) with Trust & Safety (policy).

Entity typeahead. Completes to things — people, pages, products, places — usually with an icon and a link. Ranking is dominated by the user's relationship to the entity (friends, follows, recent visits) and by entity popularity. It's mostly personalized, so it's not edge-cacheable; latency comes from pushing candidate sets to the client and from fast per-user lookups. The risk is privacy: never suggesting a private entity or revealing who someone looked up.

In-app navigation / command completion. Completes to commands, settings, documents or records inside an application. The corpus is small (thousands to a million items) and permission-filtered per user or tenant. Often the right answer is to ship the index to the client, or to run a per-tenant search index with prefix matching and typo tolerance. The risk is showing items a user isn't permitted to see.

🎯 Staff Move: "Query completion is a global, cacheable publishing problem. Entity typeahead is a per-user retrieval problem. Command completion is a permissions problem. I'll design the first and show the merge point for the second."

2.2 When NOT to Build a Typeahead Service#

  • Small corpus (< ~100K items). Ship the list to the client and filter locally. Zero server QPS, zero latency, no service to own.
  • Low traffic search boxes. If the search box gets 10 queries/s, use your search engine's completion suggester (Elasticsearch, OpenSearch, Solr) — a separate service adds an on-call rotation for no benefit.
  • Highly permissioned data with no shared ranking. If every user's visible corpus differs (e.g., enterprise documents), a shared prefix table doesn't help; use per-tenant search with prefix queries.
  • When suggestions create more risk than value. Sensitive domains (health, legal, children's products) may choose curated suggestions only, or none.

🎯 Staff Move: "If the corpus is a few thousand help articles, I'd ship them to the client as a compressed list and filter in JavaScript. A suggest service is something you build because QPS or corpus size forces you to."

2.3 What the Interviewer Leaves Underspecified#

UnderspecifiedWhy It MattersWhat to Assume Out Loud
Which intentRanking, caching and risk differQuery completion for a search box
QPSDrives caching layers350K/s avg, 1M/s peak
Number of suggestionsResponse size, cache key8–10
FreshnessTrending pipeline complexityBase daily/hourly; trending in 5–15 min
PersonalizationKills edge cachingLight: recent queries merged at serve time
LocalesNormalization, tokenization, IME40+ locales; separate index per locale
Matching semanticsPrefix only? Mid-word? Typos?Prefix of normalized query; typo fallback for long prefixes
Safety obligationsOverride layer, SLAPolicy removal within minutes; legal removals audited

2.4 Precise Terminology#

TermPrecise Meaning
PrefixThe normalized text typed so far (case-folded, Unicode-normalized, whitespace-collapsed).
Completion / candidateA full query string that starts with the prefix and is eligible to be suggested.
Top-KThe K highest-scored candidates for a prefix, precomputed at build time.
SnapshotAn immutable, versioned build of the prefix table for one locale.
OverlayA small, frequently updated set of candidates (trending) merged with the base at serve time.
OverrideA serving-time rule that removes or demotes a suggestion, applied after lookup.
k-anonymity thresholdMinimum number of distinct users who must have issued a query before it can be suggested.
DebounceClient-side delay after a keystroke before sending a request; a newer keystroke cancels the pending one.
Keystrokes savedCharacters the user didn't have to type because they accepted a suggestion — a primary quality metric.
IMEInput Method Editor — software that converts phonetic input (e.g., pinyin, romaji) into characters; the text in the box may be pre-composition.

3. The Five Fault Lines#

3.1 Fault Line 1: Precompute vs Compute at Query Time#

The tension: Precomputing top-K for every prefix makes serving a single lookup, but rankings are frozen until the next build and storage grows with the total length of all queries. Computing at query time (walk a trie, score candidates, rank) is flexible and always current, but latency scales with the subtree size — and for a 1-character prefix that subtree is the whole corpus.

StrategyWhat WorksWhat BreaksWho Pays
Query-time DFS over a trieAlways fresh; any KPrefix "s" touches millions of nodes; p99 explodesUsers (lag), on-call (latency pages)
Query-time search with edge n-gramsFlexible matching, mid-word, typos10–50ms per query in a search engine; 10–50× the cost at 1M QPSInfra budget
Precomputed top-K per prefix (Staff default)~1ms lookup; trivially cacheable; immutable snapshots roll back instantlyRankings refresh only per build; long prefixes beyond L need a fallbackBuild pipeline team owns freshness
Precomputed + query-time re-rank of top-NLookup top-50, re-rank with context (personal, locale, device)Small extra CPU; must keep N smallRanking team owns the re-ranker

Staff default: Precompute top-K (K = 10, store N = 20–50 for re-ranking headroom) for every prefix up to length L = 20 characters of the normalized query. For prefixes longer than L, take the length-L entry and filter its candidates by the full prefix; if empty, fall back to a small query-time search over a long-tail index — rare (<2% of requests) and allowed a 50ms budget.

Sizing: 50M suggestible queries × average 25 chars, but prefixes are shared heavily. Distinct prefixes ≈ 200–500M. Each entry: 20 candidate IDs × 4 bytes + scores ≈ 120–160 bytes + key ≈ 200 bytes → ~40–100 GB raw; with an FST or front-coded keys, ~20–50 GB. Pruning prefixes nobody types (no traffic in 30 days) typically cuts another 30–60%.

🎯 Staff Move: "I'll move all ranking to build time. The server's job is one hash lookup and an override filter — that's how I hit a 10ms p99 at a million QPS on boring hardware."

3.2 Fault Line 2: Freshness vs Safety & Stability#

The tension: Users expect breaking news and new products to show up in suggestions within minutes. But every shortcut from "people searched it" to "we suggest it" is a path for brigading, spam and accidental publication of something harmful — and every refresh is a chance to roll out a bad index.

StrategyWhat WorksWhat BreaksWho Pays
Real-time counts update the live indexFreshest possibleAny coordinated burst publishes itself; no review; write contention on the read pathT&S (incidents), PR, users
Daily rebuild onlyStable, reviewable, cheapBreaking news invisible for up to 24hUsers (stale), product (looks outdated)
Daily base + hourly refresh + 5–15 min trending overlay with gates (Staff default)Fresh for real trends; brigades fail distinct-user and velocity gates; overlay is small enough to reviewGating delays genuine trends by minutes; two code pathsSearch infra owns the overlay; T&S owns gate thresholds

Staff default: Base index rebuilt daily (hourly for high-churn locales), with exponential time decay (half-life ~7 days for base, ~1 day for news-heavy verticals). Trending stream computes per-query rate over 5-minute windows vs a 7-day baseline; a query enters the overlay only if rate / baseline > 5×, distinct users ≥ 500 in the window, distinct /24 networks ≥ 100, and it passes the same policy classifiers as the base. Overlay capped at ~100K entries per locale, TTL 6–24h unless promoted into the base by the next build.

When to deviate: News or sports surfaces may lower thresholds and add human review for named entities. Commerce surfaces can drop trending entirely and refresh hourly.

🎯 Staff Move: "Trending is where abuse enters. I'll gate it on distinct users and network diversity, not raw counts — 50K searches from 300 accounts is a brigade, not a trend."

3.3 Fault Line 3: Global Cacheable vs Personalized#

The tension: A global answer per (locale, prefix) is cacheable everywhere — client, CDN, server — and is cheap. A personalized answer is better for the user but uncacheable outside the client, multiplying origin QPS and latency.

StrategyWhat WorksWhat BreaksWho Pays
Fully global50–70% offloaded to client + edge; simplestIgnores the user's own history; worse for entity-like queriesUsers (relevance)
Fully personalized per requestBest relevanceNo edge caching; origin QPS ×2–3; per-user state on hot pathInfra budget; latency
Global base + personal merge (Staff default)Global part cacheable; small personal list mergedNeed a merge point; personalized responses must be marked privateRanking team owns merge logic

Staff default: Two requests or one merged response. Option A (cheapest): the client fetches the global list from the edge and merges its own locally stored recent queries (last ~50–100) that match the prefix — zero server personalization. Option B: the server merges a per-user candidate list (KV lookup, ~1ms) with global top-N and re-ranks; response marked Cache-Control: private. Start with A; add B only where measured relevance gain justifies the origin cost.

Privacy: recent-query history is user data — retention limits, deletion on request, and never shown on shared devices without a signed-in user.

🎯 Staff Move: "I'd personalize on the client first. The user's own recent searches are already on their device; merging them locally keeps the global response public-cacheable and costs us nothing."

3.4 Fault Line 4: Replicate vs Shard#

The tension: Sharding the prefix table across nodes scales memory, but prefix traffic is extremely skewed — single letters and common starts take a large share — so range sharding creates hot shards, and hash sharding still concentrates the hottest keys. Full replication avoids fan-out and hot shards but requires the whole table in every node's memory.

StrategyWhat WorksWhat BreaksWho Pays
Range shard by first letter(s)Easy to reason about's', 'c', 'a' shards take several times the load of 'x', 'q'; rebalancing is manualOn-call (hot shard pages)
Hash shard by full prefixEven key distributionHot individual keys still hit one shard; every request is one shard but cluster needs routingInfra team (router)
Full replication (Staff default when it fits)No routing, no hot shards, any node serves any prefix; scale by adding replicasEach node needs 20–50 GB RAM per locale set; snapshot distribution is heavierInfra budget (memory)
Hybrid: replicate short prefixes, hash-shard long tailHot keys everywhere; long tail partitionedTwo-tier routingInfra team

Staff default: Replicate. A 64–128 GB node holds the major locales; small locales share nodes. Group locales into serving pools. Shard only when one locale's table exceeds a node's RAM, then use the hybrid: prefixes of length ≤ 4 replicated in every shard (a few hundred MB), longer prefixes hash-sharded.

Hot-prefix math: at 1M QPS peak with ~60% absorbed upstream, origin sees ~400K QPS. If prefix "a" alone is ~1% of origin traffic (4K QPS), any node can serve it from RAM in microseconds — it's only a problem if you sharded it onto one node.

3.5 Fault Line 5: Central Policy vs Product Autonomy#

The tension: A central suggestion policy and override service guarantees consistent safety across every suggester, but slows product teams that need domain-specific curation. Per-surface policies move fast and fit their domain, but the weakest surface determines the company's exposure.

StrategyWhat WorksWhat BreaksWho Pays
Each surface owns its blocklistFast, domain-specificInconsistent; the next incident comes from the weakest list; legal removals must be applied N timesT&S (N integrations), legal, PR
Central policy, central curation of everythingConsistentT&S becomes a bottleneck for product-specific tuningProduct velocity
Central safety floor + surface-owned ranking/curation (Staff default)One override service and policy floor; surfaces add stricter rules and own rankingRequires an override API every surface integratesPlatform builds it once

Staff default: One override service, one audit log, one propagation SLA. Every suggester calls it (or loads its compiled deny-set) on every response. Surfaces may add stricter local rules; they may not opt out of the floor.


4. Failure Modes & Operational Reality#

4.1 Bad Index Rollout — The Empty (or Wrong) Suggestions#

Scenario: A build bug (a normalization change) produces a snapshot where every prefix containing an apostrophe maps to nothing, and many accented prefixes map to the wrong candidates. It rolls out to all nodes at once.

t=0:       New snapshot en-US-2026-10-01T03 swapped on all nodes
t=+1min:   suggest.empty_response_rate jumps from 2% to 9%
t=+5min:   Suggestion CTR drops 15%; search volume dips slightly
t=+20min:  On-call notices dashboard; no alert fired (threshold 15%)
t=+25min:  Rollback to previous snapshot (atomic swap, seconds)

Detection: suggest.empty_response_rate per locale (alert at 2× baseline); suggest.ctr canary vs control; build-time validation against a golden set of 10K prefixes.

Mitigation: Immutable snapshots make rollback a pointer swap. Staged rollout: 1 node → 5% → 50% → 100% with automatic comparison of empty-rate and CTR between versions at each stage.

Owner: Search infra (build + rollout). Golden set maintained jointly with ranking.

4.2 Hot Prefix / Cache Stampede After Purge#

Scenario: T&S purges all edge-cached prefixes starting with a celebrity's name after a removal. The name is trending; tens of thousands of users are typing it. Every keystroke misses the edge simultaneously.

t=0:       Purge of 1,200 prefixes across the CDN
t=+1s:     Origin QPS for those prefixes: 0 → 60K/s
t=+5s:     Suggest nodes fine (RAM lookup), but the API gateway's
           connection pools to the region saturate
t=+30s:    p99 for all suggest traffic in region: 15ms → 400ms

Detection: edge.origin_requests spike; gateway.pool_utilization; suggest.p99 by region.

Mitigation: Edge request coalescing (one origin fetch per key per POP, others wait); purge as soft purge (serve stale while revalidating for non-safety purges); for safety purges, replace with the corrected response rather than delete (push-to-edge).

Owner: Edge/CDN team owns coalescing; search infra owns purge strategy.

4.3 Brigaded Suggestion — Coordinated Search Abuse#

Scenario: A coordinated group searches " scam" 80K times across 2 hours to push it into suggestions for "".

Detection: Trending gate rejects it (distinct users 900, but distinct /24 networks 40 and account age median 2 days). trending.rejected_by_gate{reason} spikes; T&S review queue receives it.

Mitigation: Gates on user and network diversity; account-age and reputation weighting in aggregation (new and low-reputation accounts count less); base-index aggregation over ≥7 days dilutes bursts.

Owner: T&S owns thresholds and review; search infra owns gate implementation.

4.4 Override Propagation Lag — The Removal That Didn't Stick#

Scenario: Legal requires removal of a defamatory suggestion. T&S adds the override. It's applied on nodes within 2 minutes — but mobile apps cache suggestion responses locally for 24h, and the CDN TTL is 15 minutes. Users keep seeing it.

t=0:       Override created
t=+2min:   All serving nodes filter it
t=+15min:  Edge caches expire
t=+24h:    Mobile client caches expire

Detection: Synthetic probes from multiple regions and app versions query the affected prefix and assert absence; override.propagation_seconds p99 per layer.

Mitigation: Push the override deny-set to clients too (small, compiled bloom/hash set, refreshed with app config every few minutes); client filters cached responses against it. Edge: targeted purge for affected prefixes. Client cache TTL ≤ 1h for suggestions.

Owner: Search infra owns the SLA across all layers, including clients; mobile team owns client-side filter.

4.5 Latency Regression — p99 Doubles After a Ranking Change#

Scenario: The ranking team adds a serve-time re-ranker with a feature lookup. p50 is fine; p99 goes from 12ms to 45ms due to KV tail latency.

Detection: suggest.latency_p99 per stage (lookup, re-rank, override, serialize); alert at 25ms.

Mitigation: Hard per-stage deadlines: if the re-ranker misses 5ms, return the base ranking. Hedged KV requests at p95.

Owner: Ranking team owns the re-ranker budget; search infra enforces the deadline.

4.6 Locale Normalization Bug — Turkish I#

Scenario: A generic lowercase function maps "İstanbul" incorrectly; Turkish users typing "is" don't see Istanbul-related suggestions, and some prefixes collide.

Detection: Per-locale golden sets; suggest.ctr and empty_rate broken down by locale.

Mitigation: Locale-aware case folding and normalization (NFKC + locale rules) shared between build and serve as one library.

Owner: Search infra (internationalization specialist), with locale QA.

4.7 Operational Reality Matrix#

FailureDetection SignalBlast RadiusMitigationOwner
Bad snapshotsuggest.empty_response_rate, CTR canaryOne locale, all usersStaged rollout, pointer-swap rollbackSearch infra
Stampede after purgeedge.origin_requests spike, suggest.p99RegionRequest coalescing, push-replaceEdge + search infra
Brigaded suggestiontrending.rejected_by_gate, T&S queueBrand/person, PRDiversity gates, reputation weightingT&S + search infra
Override lagSynthetic probes, override.propagation_secondsLegal exposureClient deny-set, targeted purgeSearch infra + mobile
Latency regressionPer-stage p99All usersStage deadlines, fallback rankingRanking + search infra
Normalization bugPer-locale golden setsOne localeShared normalization librarySearch infra i18n
Pipeline stalled (no new snapshot)index.age_hours > 36Freshness onlyServe last good snapshot; alertSearch infra
Personal KV downpersonal.error_ratePersonalized usersServe global onlySearch infra

5. Evaluation Rubric#

5.1 Level-Based Signals#

DimensionSenior (L5)Staff (L6)Principal (L7)
FramingOne autocompleteThree intents; picks one; latency budget per keystrokeInventories suggesters across the org; shared safety pipeline
Data structureTrie with top-KPrecomputed, immutable, versioned snapshots; fallback for long tailIndex as a published artifact with a build contract reused by surfaces
CachingRedis in front of the serviceClient + edge + server, with hit rates and the personalization bitPrices keystrokes by layer; sets client caching policy org-wide
FreshnessReal-time updatesBase + overlay with diversity gatesFreshness tiers negotiated with T&S per surface
SafetyProfanity listk-anonymity, classifiers, overrides with propagation SLA across all layersPublished policy, removal SLA as org SLO, audit, legal workflow
Operations"Add replicas"Staged rollout, golden sets, per-stage deadlines, named ownersGame days for removal propagation; error budget for suggestion quality

5.2 Strong Hire Signals#

SignalWhat It Sounds Like
Derives from the budget"100ms per keystroke means 10–20ms for the server. That rules out query-time ranking."
Moves work offline"Ranking happens in the build. Serving is a hash lookup and a filter."
Layers caching"Client and edge take most keystrokes; personalization decides which responses can be cached publicly."
Treats suggestions as published content"A suggestion is something we say. It needs a k-anonymity threshold and a removal SLA."
Handles non-Latin input"For Japanese, the box holds kana pre-conversion — I index readings, not just surface text."

5.3 Lean No-Hire Signals#

SignalWhy It Misses the Bar
Updates the live trie on every searchWrite contention on the hottest read path and no safety gate
Shards by first letter without addressing skewPredictable hot shards
No caching discussion beyond RedisMisses where 50–70% of traffic should be absorbed
No answer to "remove this suggestion now"Misses the most consequential operational requirement
Personalizes everything server-side without costing itDestroys cacheability; multiplies origin load

5.4 Common False Positives#

  • Detailed trie memory layout ≠ typeahead design. Knowing pointer sizes doesn't show you can meet a latency budget or remove a suggestion in minutes.
  • "Use Elasticsearch completion suggester" ≠ done. It's a fine engine choice; it doesn't answer freshness, safety, caching or personalization.
  • Real-time freshness enthusiasm ≠ product judgment. Faster trending is not free; it's abuse surface.
  • Knowing Unicode normalization forms ≠ multi-language design. IME, segmentation and transliteration are the hard parts.

6. Interview Flow & Pivots#

6.1 Typical 45-Minute Shape#

PhaseTimeGoal
Framing0–4 minIntent, QPS, latency budget, safety requirement
Entities + API4–6 minPrefix entry, snapshot, override; personalization bit
Architecture6–11 minClient → edge → serving → build + trending + overrides
Deep dive 111–19 minLatency budget and caching layers
Deep dive 219–27 minBuild pipeline and freshness
Deep dive 327–35 minSafety or personalization
Operations35–41 minRollout, failure matrix, owners
Wrap-up41–45 minMulti-language, evolution

6.2 How Interviewers Pivot — And What They're Testing#

PivotWhat They're TestingStrong Response
"Make it work for Chinese and Japanese"i18n depthIndex by reading (pinyin, kana) and surface form; IME composition events; no whitespace tokenization
"Support typos"Fallback designTypo-tolerant only for prefixes ≥ 4 chars on a miss; edit distance 1 via precomputed deletes or FST Levenshtein automaton
"Show trending within 1 minute"Freshness vs safetyPossible for curated entities; for open queries, minutes are the safety budget
"We need per-user suggestions"CacheabilityClient-side merge first; server merge only where measured
"Cut serving cost by half"Cost leversRaise edge TTL for short prefixes, increase debounce, prune unused prefixes

6.3 What to Deliberately Skip#

  • Full-text retrieval and ranking for the search results page — reference Search Indexing
  • Trie node memory layout beyond one sentence
  • Load balancer details
  • ML ranking model architecture — name features and the latency budget instead

6.4 Follow-Up Questions to Expect#

  1. "How big is the index, and does it fit in memory?"
  2. "How do you roll out a new index without a bad version reaching everyone?"
  3. "A suggestion must be removed in 10 minutes. Walk me through every layer."
  4. "How do trending queries get in, and what stops abuse?"
  5. "What's cached where, and what's the hit rate at each layer?"
  6. "How do you handle a prefix longer than your precomputed length?"
  7. "How do you evaluate whether a ranking change made suggestions better?"

7. Active Drills#

Drill 1: The Opening (Intent + Budget)#

Prompt: "Design autocomplete for our search box."

Staff Answer

"Before the data structure — which typeahead? Query completion, entity typeahead for people and pages, and in-app command completion rank, cache and fail differently. I'll design query completion for the main search box, and show where per-user entities merge in.

Scale: 5B searches/day, ~6 suggest requests per search after a 75ms debounce — ~350K requests/s average, ~1M/s peak. Budget: 100ms keystroke-to-render; RTT takes 20–80ms, rendering ~10ms, so the server gets 10–20ms p99. That budget rules out ranking at query time, so I'll precompute top-K per prefix offline and serve lookups from memory.

Non-functional I want to state now: suggestions are content we publish. Nothing becomes suggestible unless enough distinct users searched it, and any suggestion must be removable everywhere — including caches — within 10 minutes.

I'll go: budget and caching layers → build pipeline → freshness → safety → personalization → multi-language."

Why this is L6:

  • Picks an intent and explains why the others differ
  • Derives architecture from a latency budget with arithmetic
  • Puts safety and removal SLA in the requirements, not in a follow-up

What L7 adds:

  • Asks how many suggesters the company runs and whether a shared safety service exists — a new one with its own blocklist is a liability
  • Names the owners up front: search infra for mechanism, T&S for policy, legal for removals
❌ Common L5 Trap

"I'll use a trie. Each node stores children and a frequency. On a query, I walk to the prefix node and do a DFS to find the top 10 by frequency."

Why this misses: The interviewer asks "how long does the DFS take for prefix 'a'?" — and the answer is "it visits most of the corpus." The design never set a budget, so it never noticed that query-time ranking can't meet it.


Drill 2: The Build Pipeline#

Prompt: "Walk me from raw query logs to what's in memory on a serving node."

Staff Answer

"Seven stages, run daily per locale (hourly for high-churn locales):

  1. Collect: search logs (query text, locale, user key, timestamp, clicked suggestion?) flow through Kafka to the lake.
  2. Normalize: NFKC, locale-aware case folding, whitespace collapse, strip trailing punctuation. The same library runs at serve time.
  3. Aggregate with decay: score = Σ over days of count_d × 0.5^(age_d / 7), plus a CTR term from suggestion acceptance. Count distinct users per query.
  4. Privacy filter: drop queries with < k distinct users over the window (e.g., k = 100 over 30 days). This removes the vast majority of distinct raw queries and all personal-looking ones.
  5. Policy filter: blocklists, classifiers (sexual, hate, violence, dangerous, personal information), entity-specific rules. Rejected queries are logged with reason.
  6. Build top-K: for every prefix of length 1..20 of every surviving query, keep top 20 by score. Emit as an FST or sorted key-value file with a checksum.
  7. Validate and publish: golden-set test (10K prefixes with expected results), size and empty-rate checks, diff vs previous snapshot. Upload to object storage; nodes pull and swap atomically in a staged rollout.

The serving node loads the snapshot into memory (memory-mapped), builds no structures at load, and swaps a pointer."

Why this is L6:

  • Privacy and policy filters are pipeline stages, not afterthoughts
  • Normalization shared between build and serve to prevent mismatches
  • Validation and staged rollout treat the index as a deployable artifact

What L7 adds:

  • Makes the build a shared platform: surfaces plug in their own scoring function but inherit privacy and policy stages they can't skip
  • Records the policy version in the snapshot metadata so every suggestion shown can be traced to the policy that admitted it
❌ Common L5 Trap

"Every time someone searches, increment the count in the trie and update top-K up the path."

Why this misses: It turns the hottest read structure into a write-heavy one, and it publishes any query the moment it's searched enough — no privacy threshold, no policy check.


Drill 3: The Latency Budget — Make It Concrete#

Prompt: "Where does the 100ms go, and what do you cache where?"

Staff Answer

"Budget per keystroke:

SegmentBudget
Debounce50–100ms (not counted if the user is still typing)
Client cache check< 1ms
Network RTT to edge10–40ms
Edge hit~1ms
Edge → origin (on miss)20–40ms
Server lookup + override + serialize2–10ms
Render~10ms

Caching layers:

  • Client: cache every response by (locale, prefix) for the session. Backspace and retyping hit the cache. Also: if the response for 'how to t' has fewer than K results, the result for 'how to ti' is a filter of it — no request needed. Typically 15–25% of keystrokes.
  • Edge: non-personalized responses, TTL 5–15 min, cache key = (locale, normalized prefix, client type). Short prefixes are few and hot, so hit rates for length ≤ 3 are >95%; overall edge hit rate 40–60% of requests reaching it.
  • Server: the whole index is in RAM; no separate cache.

Net: origin sees ~30–50% of keystroke requests."

Why this is L6:

  • Itemizes the budget and shows the server is a small slice
  • Uses the prefix-subsumption trick on the client
  • Separates personalized from cacheable responses in the cache key

What L7 adds:

  • Prices it: edge requests cost ~10× less than origin requests at this scale; raising edge TTL from 5 to 15 min for prefixes ≤ 3 cuts origin load measurably with no freshness loss users notice
  • Sets an org-wide client caching policy so the mobile app doesn't cache suggestions for 24h (which breaks removals)
❌ Common L5 Trap

"Put Redis in front of the service to cache popular prefixes."

Why this misses: The index is already in RAM; Redis adds a network hop and saves nothing. The caches that matter are the ones closer to the user.


Prompt: "A celebrity announces a new album at 9pm. Users expect it in suggestions by 9:15. How?"

Staff Answer

"A streaming job reads the query log (Kafka) and maintains per-query counts in 5-minute tumbling windows, plus a 7-day baseline per query from the base index. A query is a trending candidate when:

  • rate in the last window ≥ 5× its baseline (or it's new with ≥ N searches),
  • ≥ 500 distinct users and ≥ 100 distinct /24 networks in the window,
  • median account age and reputation above thresholds,
  • it passes the same policy classifiers as the base build.

Candidates are added to a per-locale overlay (cap ~100K entries) and pushed to serving nodes every 1–5 minutes. At serve time, the node merges overlay candidates for the prefix with base top-K using a calibrated score, applies overrides, returns top 10. Overlay entries expire after 24h unless the next base build picks them up.

So the album shows up within ~10 minutes. A brigade from a few hundred accounts on a handful of networks doesn't."

Why this is L6:

  • Uses diversity gates, not raw counts
  • Keeps the trending path small and separately expiring
  • Applies the same policy classifiers to the fast path

What L7 adds:

  • Lets T&S tune gate thresholds per locale and vertical without a deploy, with an audit log
  • Decides with T&S that trending entries mentioning named individuals require human review in sensitive verticals — accepting slower freshness there on purpose
❌ Common L5 Trap

"Lower the rebuild interval to every minute."

Why this misses: Rebuilding a 50 GB index every minute is expensive, and it publishes everything — including brigades — at the same speed as real news.


Drill 5: Hot Prefix and Hot Shard#

Prompt: "You sharded by first letter. The 's' shard is at 90% CPU and 'x' is idle. Fix it."

Staff Answer

"First question: why shard at all? A filtered prefix table for a large locale is ~20–50 GB. That fits on a 64–128 GB node. I'd replicate the full table to every node in the locale's pool and load-balance round-robin — no routing, no hot shards, add replicas linearly for QPS.

If the table genuinely doesn't fit — say a multi-locale pool at 300 GB — I'd shard by hash of the full normalized prefix (even key spread) and replicate prefixes of length ≤ 4 to every shard (a few hundred MB), because those are the hottest keys. Routing is by hash; any node can answer a short prefix locally.

And upstream, the edge absorbs >95% of length ≤ 3 prefixes anyway, so origin hotness is dampened."

Why this is L6:

  • Questions the sharding premise with arithmetic
  • Uses hybrid replication for the specific skew of prefix traffic
  • Remembers the edge already flattens the hottest keys

What L7 adds:

  • Compares cost: replication multiplies RAM (N replicas × 50 GB) but removes a routing tier and an on-call class; at typical cloud prices the RAM is cheaper than the operational complexity until tables reach hundreds of GB
❌ Common L5 Trap

"Split 's' into 'sa–sm' and 'sn–sz'."

Why this misses: It's manual rebalancing that has to be redone whenever traffic shifts, and it never questions whether sharding was needed.


Drill 6: Personalization Without Losing the Cache#

Prompt: "Product wants suggestions personalized by the user's history. How do you add that without blowing up cost?"

Staff Answer

"Three levels, cheapest first:

  1. Client-side recent queries: the app stores the user's last ~100 searches locally. On each keystroke, it merges matching recent queries (marked with a history icon) above the global list from the edge. Zero server cost; global response stays public-cacheable.
  2. Contextual but cacheable: coarse context like country, city or device type goes into the cache key. Cardinality stays bounded (hundreds of values), so edge caching still works.
  3. Server-side personal re-rank: for signed-in users where measurement shows gains, the server fetches a per-user candidate set (~1ms KV lookup — recent queries, followed entities) and re-ranks global top-20. Response is Cache-Control: private. I'd expect this to roughly double origin requests for affected users, so it should be justified by a measured CTR or keystrokes-saved gain.

Privacy: history is deletable, retention-limited, and personalized suggestions never appear on signed-out or shared-device sessions."

Why this is L6:

  • Orders options by cost and keeps the common path cacheable
  • Uses bounded-cardinality context in cache keys
  • States the privacy obligations of personalization

What L7 adds:

  • Requires a privacy review because personalization changes what the company stores about users
  • Makes "personalization lift per dollar" a decision metric reviewed with product
❌ Common L5 Trap

"Build a per-user trie of their search history and query it with the global trie."

Why this misses: Per-user tries for hundreds of millions of users is a large storage problem for a merge that a client can do locally with a 100-item list.


Drill 7: Build vs Buy#

Prompt: "Why not just use Elasticsearch's completion suggester?"

Staff Answer

"For many products, I would. Below ~10K QPS, with a corpus under tens of millions of entries and no heavy safety requirements — an e-commerce catalog, a docs site — the completion suggester is a fine choice: in-memory FST, weights, context filters, and the team probably already runs Elasticsearch.

At 1M QPS for web-scale query completion I'd build a dedicated lookup service, because:

  • the serving logic is trivial (hash lookup + filter) and a dedicated service hits 10ms p99 on far fewer nodes than a general search cluster;
  • the build pipeline — privacy thresholds, policy, decay, trending — is the real system, and it lives outside Elasticsearch either way;
  • immutable snapshot swaps and staged rollouts are simpler than reindexing a live cluster.

So: buy the engine at small scale; at large scale the 'engine' is ~1,000 lines and the pipeline is the product."

Why this is L6:

  • Gives a threshold for each answer, not a blanket preference
  • Identifies where the real complexity lives (pipeline, not lookup)

What L7 adds:

  • Considers who will run it in 3 years: a bespoke service needs an owning team; if search infra already runs Elasticsearch fleet-wide, the marginal cost of one more use case may win even at moderate scale
❌ Common L5 Trap

"Elasticsearch handles search, so it handles autocomplete."

Why this misses: It treats autocomplete as a query feature, ignoring the latency budget, the QPS multiple over search and the build-time safety pipeline.


Drill 8: Changing the Blocklist or Ranking Without an Incident#

Prompt: "T&S wants a new classifier that removes 'dangerous' suggestions. How do you ship it?"

Staff Answer

"A new classifier changes what millions of users see, so it ships like a ranking change:

  1. Shadow: run it in the build, but only log what it would remove. Report: % of prefixes affected, top-1,000 removed suggestions by traffic, per locale.
  2. Review: T&S samples removals for false positives — 'how to kill a python process' shouldn't go. Tune thresholds or add allowlists.
  3. Canary: ship a snapshot with the classifier to 1% of traffic. Compare suggestion CTR, keystrokes saved, empty-rate and complaint reports vs control.
  4. Roll out by locale, starting with locales with the best classifier quality.

Emergency removals don't go through this path — they use overrides, which take effect in minutes."

Why this is L6:

  • Measures false positives before enforcement
  • Uses snapshot canaries with quality metrics
  • Separates policy evolution (slow, measured) from emergency removal (fast)

What L7 adds:

  • Requires per-locale classifier quality bars before enabling — a classifier that's good in English may be poor in Thai
  • Publishes policy changes in the external suggestion policy so users and regulators see a consistent standard
❌ Common L5 Trap

"Add the classifier to the pipeline and run the next build."

Why this misses: A 2% false-positive rate across billions of suggestions removes legitimate completions at scale with no measurement until users complain.


Drill 9: Cost — Halve the Serving Bill#

Prompt: "Typeahead costs more than search itself. Cut it in half."

Staff Answer

"Typeahead often costs more because it gets ~5–8× the requests. Levers, by safety:

  1. Debounce 50 → 100ms on desktop: cuts requests ~20–30% for fast typists, imperceptible.
  2. Client prefix subsumption: if a response had fewer than K results, filter it locally for longer prefixes — removes 10–15% of requests.
  3. Edge TTL up for short prefixes (5 → 30 min for length ≤ 3): those change slowly; trending is merged at origin for longer prefixes.
  4. Prune the index: drop prefixes with no traffic in 30 days; often 30–60% smaller, so fewer, smaller nodes.
  5. Move server-side personalization back to the client where lift is marginal.

Together these typically cut origin requests 40–60%. I would not reduce replicas below N+2 per region or relax the override SLA."

Why this is L6:

  • Attacks request volume, not just node cost
  • Each lever has an expected effect and a risk
  • Protects reliability and safety from cost cuts

What L7 adds:

  • Reframes the metric as cost per 1,000 searches assisted, tied to the revenue value of searches — suggestions that save keystrokes drive more completed searches, so the right budget is relative, not absolute
❌ Common L5 Trap

"Use smaller instances."

Why this misses: The index must fit in RAM; smaller instances force sharding and routing — more complexity, likely more cost.


Drill 10: Multi-Language and Multi-Region#

Prompt: "We're launching in Japan, China and the Middle East. What changes?"

Staff Answer

"Three things change: input, matching and policy.

  • Input (IME): in Japanese and Chinese, the text in the box during composition is often phonetic (romaji/kana, pinyin) before conversion. The client must decide whether to send composition-state text; I'd send it and index readings alongside surface forms, so 'とうき' and 'touki' can match '東京' completions. Pinyin initials ('bj' → '北京') are common and need their own prefix keys.
  • Matching: no whitespace word boundaries in CJK — prefix matching on the full string works for query completion, but mid-string matching needs a segmenter. Arabic and Hebrew are right-to-left with optional diacritics; normalize by stripping diacritics and unifying letter variants (e.g., alef forms). Turkish needs locale-aware casing.
  • Policy: classifiers and blocklists are per locale and need native-speaker review; legal removal requirements differ by country.

Per-locale snapshots served from in-region pools (Tokyo, Hong Kong/Singapore, Middle East region) keep RTT low; the build pipeline runs centrally unless data-residency rules require in-country processing of query logs."

Why this is L6:

  • Knows IME composition and readings are the core CJK problem
  • Handles normalization differences per script
  • Treats safety as per-locale, not translated

What L7 adds:

  • Checks data-residency obligations before choosing a central build — in some jurisdictions query logs must be processed in-country, which turns one pipeline into several
  • Staffs locale expertise: native-speaker T&S reviewers are a headcount decision that gates launch
❌ Common L5 Trap

"Use UTF-8 and the same trie."

Why this misses: Encoding is not the problem; matching phonetic input to characters and per-locale policy are.


8. Deep Dive Scenarios#

Deep Dive 1: Peak-Traffic Incident — Breaking News Spike#

Context: A major breaking news event. Search traffic triples in 5 minutes; suggest traffic quadruples because users type slowly and retype. Origin p99 goes from 12ms to 300ms in two regions, and suggestions for the event's keywords are stale (the trending overlay is 20 minutes behind). The on-call escalates to you.

Questions to Surface First:

  • Is the latency at the suggest nodes, the gateway, or the edge-to-origin path?
  • Why is the overlay 20 minutes behind — is the streaming job lagging or blocked by gates?
  • What's the edge hit rate now vs normal? Did a deploy or purge reset caches?
  • Are we near node memory or CPU limits, or connection limits?

Typical L5 Approach: Scales out suggest nodes and waits. Checks the stream job's lag and restarts it. Correct moves, but new nodes take minutes to load a 40 GB snapshot, and restarting the stream job adds more lag.

Staff Approach: Finds that edge hit rate dropped from 55% to 20%: event keywords are new prefixes not in cache, and the edge TTL is short for long prefixes. Enables edge request coalescing (if not on), raises edge TTL for non-personalized responses to 5 minutes during the event, and turns off server-side personalization (load-shedding mode) to make more responses cacheable. Overlay lag: the stream job is fine; the gate requires 500 distinct users over a full 5-minute window and the window just closed — expected. Origin p99 recovers in 3 minutes without new nodes.

Principal Approach: Writes a "surge mode" into the design: a single switch that raises edge TTLs, disables server-side personalization, and increases debounce via remote config — pre-approved by product as a degraded mode. Sets a capacity target in terms of origin QPS at 0% edge hit rate for the top 1,000 prefixes, and adds a quarterly load test simulating a news spike. Makes the trending-latency SLO explicit and agreed with T&S, so "20 minutes behind" isn't an incident when it's the agreed safety budget.

Staff Approach — Full Reasoning
PhaseWhat to Do
Immediate (0–5 min)Check edge hit rate and origin QPS by region; enable coalescing; extend TTLs via config
TriagePer-stage latency on nodes; gateway pool usage; overlay lag vs gate window
Quick fixSurge mode: disable server personalization, raise debounce 75 → 150ms via remote config
GuardrailsKeep override propagation working — surge mode must not slow removals
Post-mortemPre-warm nodes; snapshot load time; capacity at low edge hit rate

Metrics to Watch: edge.hit_rate, edge.origin_requests, suggest.p99{stage}, gateway.pool_utilization, trending.overlay_age_seconds, node.snapshot_load_seconds.

Organizational Follow-up: Product signs off on the surge-mode degradations ahead of time. Snapshot loading time becomes a tracked metric (target < 60s via memory-mapped files).

Ownership Question: "Who decides to switch on surge mode?" Staff answer: The search-infra on-call, without escalation, because the degraded behavior is pre-approved by product. That's the point of pre-approving it.

Key Takeaway: "In a surge, the edge is your capacity. Design the degraded mode that makes more responses cacheable, and get it approved before the surge."

What clears the Staff bar:

  • Finds the bottleneck at the cache layer rather than scaling the origin blindly
  • Distinguishes expected trending latency from a fault
  • Uses pre-approved degraded modes

Deep Dive 2: Silent Failure — The Index That Stopped Updating#

Context: A product manager notices that suggestions for a product launched 9 days ago never appear. You find the base index for three locales hasn't been rebuilt in 11 days. No alert fired. Serving is healthy.

Questions to Surface First:

  • Did the build fail, or succeed but fail validation, or succeed and fail to publish?
  • Why didn't anything alert on snapshot age?
  • Are other locales also stale but unnoticed?
  • Is the trending overlay masking staleness for popular queries?

Typical L5 Approach: Finds the failed job (a schema change in the log table broke the normalization step for those locales), fixes it, reruns the build, adds an alert on job failure.

Staff Approach: Fixes and rebuilds, but alerts on the outcome, not the job: index.age_hours per locale on serving nodes, paging at 36h. Also finds that validation correctly rejected two builds — but rejection went to a log, not a person. Adds a contract test for the log schema the pipeline depends on, owned jointly with the logging team.

Principal Approach: Generalizes: any system serving precomputed artifacts (suggestions, recommendations, spell-correction dictionaries, ranking models) can go silently stale. Introduces a standard: every served artifact exposes its age and version as a metric, with an owner and a max-age SLO. Adds upstream schema changes to a change-review process with downstream consumers registered.

Staff Approach — Full Reasoning
DimensionStaff Answer
Root causeUpstream schema change broke normalization; validation rejected builds; rejection unalerted
Immediate actionFix parser, rebuild the three locales, staged rollout
System fixAlert on index.age_hours at serving, not job status
Process fixSchema contract tests; registered downstream consumers for log tables
Broader questionWhich other served artifacts have no age metric?

Metrics to Watch: index.age_hours{locale}, build.validation_rejections_total, build.success{locale}, trending.overlay_share (rising share of results from overlay can indicate stale base).

Organizational Follow-up: Logging team adds search-suggest as a registered consumer; schema changes require their sign-off.

Ownership Question: "Who owned noticing the stale index?" Staff answer: Search infra — the serving team owns the freshness of what it serves, regardless of which pipeline produced it. Job-level alerts were a proxy; age at serving is the real signal.

Key Takeaway: "Alert on the age of what you serve, not on the success of the job that builds it."

What clears the Staff bar:

  • Moves alerting from process to outcome
  • Notices validation rejections need a human owner
  • Fixes the upstream contract, not just the parser

Deep Dive 3: Large Onboarding — A New Surface Adopts the Platform#

Context: The shopping team wants to retire its own autocomplete and use the shared suggestion platform. They have 80K QPS peak, a catalog of 200M products, need suggestions to reflect price drops and inventory within 15 minutes, and have their own blocklist of 30K terms. Launch in 6 weeks.

Questions to Surface First:

  • Are they completing queries or products (entities)? Probably both.
  • Does inventory need to be in the suggestion index, or can it be a serve-time filter?
  • Is their blocklist stricter than the platform floor, looser, or overlapping?
  • Who owns relevance regressions after migration — them or the platform?

Typical L5 Approach: Adds a "shopping" locale-like index to the build, imports their blocklist, and capacity-plans for 80K QPS.

Staff Approach: Splits their needs: query completion uses the shared build with a shopping-specific scoring function plug-in; product entity completion is a separate candidate source (top products per prefix) merged at serve time; inventory and price are serve-time filters from a small fast store, so the index doesn't rebuild every 15 minutes. Their blocklist is layered on top of the platform floor as surface-specific rules. Migration runs in shadow for 2 weeks comparing CTR against their current system.

Principal Approach: Uses the onboarding to define the platform's surface contract: what a surface can customize (scoring plug-in, candidate sources, stricter rules, UI) and what it cannot (privacy threshold, policy floor, override service, propagation SLA). Agrees on an SLO split — platform owns availability and latency; surface owns relevance — and a chargeback model so capacity growth is funded by the surface that drives it.

Staff Approach — Full Reasoning
PhaseWhat to Do
Week 1–2Define scoring plug-in and entity candidate source; serve-time inventory filter design
Week 2–3Import blocklist as surface rules; T&S reviews overlap with platform floor
Week 3–4Capacity: 80K QPS on dedicated pool; load test with their traffic replay
Week 4–6Shadow: serve both, log both, compare CTR and keystrokes saved; cut over by percentage

Metrics to Watch: suggest.ctr{surface}, suggest.keystrokes_saved{surface}, suggest.p99{surface}, inventory_filter.drop_rate.

Organizational Follow-up: Platform runbook includes surface-specific escalation contacts; shopping on-call handles relevance pages.

Ownership Question: "If shopping suggestions get worse after migration, who's paged?" Staff answer: Shopping's on-call for relevance metrics; platform on-call for latency and availability. That split is written into the onboarding contract before launch.

Key Takeaway: "Onboarding is where a platform defines its contract. Write down what surfaces can customize and what they can't before the first one migrates."

What clears the Staff bar:

  • Moves fast-changing data (inventory) to serve-time filters
  • Layers surface rules on a non-negotiable floor
  • Splits SLO ownership explicitly

Deep Dive 4: Post-Mortem — The Suggestion That Made the News#

Context: For six hours, typing a politician's name suggested a false and defamatory completion. It reached social media and press. The suggestion passed the privacy threshold (thousands of distinct users searched it after a viral post) and no classifier flagged it. You're leading the post-mortem.

Questions to Surface First:

  • Did it enter via the base build or the trending overlay?
  • How long from first report to removal at each layer — nodes, edge, clients?
  • Were there earlier signals (user reports, T&S queue) that were missed?
  • Are other public figures exposed to the same pattern right now?

Typical L5 Approach: Adds the phrase to the blocklist, rebuilds, and adds the politician's name to a sensitive-entity list.

Staff Approach: Timeline shows it entered via trending (legitimate diversity — a real viral wave), removal took 6 hours because the override console required an engineer, and mobile clients cached it for 24h. Fixes: T&S operators can create overrides directly (audited); client-side deny-set; edge push-replace. Pattern fix: completions pairing a person entity with an allegation-type term ("is a", "arrested", "affair") require classifier scoring and, above a traffic threshold, human review before entering trending.

Principal Approach: Treats it as a policy and governance gap, not a classifier gap. Establishes an org-level suggestion policy for named individuals with legal and policy teams, a defined removal SLA (e.g., 30 minutes from report for high-severity), a 24/7 T&S staffing decision for that SLA, and quarterly removal-propagation drills across web, mobile and edge. Reports time-to-removal to leadership as a risk metric alongside availability.

Staff Approach — Full Reasoning
PhaseWhat to Do
ImmediateOverride at nodes; targeted edge push-replace; client deny-set push via config
ScopeScan current trending and base for person + allegation patterns; review top hits
RemediationDirect T&S override access; audit log; client cache TTL ≤ 1h
GuardrailsPerson-entity + allegation classifier; human review threshold on trending
Post-mortemRemoval latency per layer; why reports didn't escalate; on-call access path

Metrics to Watch: override.propagation_seconds{layer}, tns.report_to_removal_minutes, trending.person_allegation_candidates, synthetic probe failures.

Organizational Follow-up: Removal drill each quarter: create a test override and measure time to absence at nodes, edge and each client version.

Ownership Question: "Who decides what's removed?" Staff answer: Trust & Safety under a written policy, with legal for court orders. Search infra owns the mechanism and its SLA — engineers should never be the ones judging content at 2am.

Key Takeaway: "The removal path is the safety system. Measure it end to end, including caches you don't own."

What clears the Staff bar:

  • Measures removal latency per layer, including clients
  • Separates policy decision (T&S) from mechanism (infra)
  • Fixes the pattern class, not the single string

Deep Dive 5: Multi-Region Expansion#

Context: Typeahead runs from two US regions. Leadership wants sub-50ms p95 keystroke latency in Europe, India and Southeast Asia, and EU data protection review flagged that EU users' query logs are processed in the US.

Questions to Surface First:

  • What's current p95 for those users, and how much is RTT vs server?
  • Does the EU requirement apply to query logs (personal data) or also to derived aggregates?
  • Do overrides need to be global, regional, or both (some removals are country-specific)?
  • What's the edge hit rate in those regions?

Typical L5 Approach: Deploys suggest nodes in each new region, replicates the snapshot store, and routes users by geo-DNS.

Staff Approach: Serving is easy to regionalize because snapshots are immutable files — replicate them to regional object storage, nodes pull locally. The harder part is the build: EU query logs are processed in an EU pipeline that produces EU-locale snapshots; aggregated, k-anonymized prefix tables can then be served anywhere. Overrides are a global replicated set with per-country scoping (a court order in one country applies only there). Edge caching in those regions handles most traffic.

Principal Approach: Separates two decisions with different owners and reversal costs: serving locality (an engineering decision, two-way door) and data processing location (a legal one-way door). Gets legal to confirm in writing that k-anonymized aggregates are outside personal-data scope before designing around it. Prices the regional build pipelines (each adds compute plus an on-call surface) and consolidates where regulation permits.

Staff Approach — Full Reasoning
PhaseWhat to Do
Measurep95 split into RTT / edge / origin per region
ServeRegional pools; snapshot replication; geo routing with failover to nearest region
BuildEU pipeline for EU logs; aggregates exported after k-anonymity
OverridesGlobal set with country scope; replicated with < 1 min lag
RolloutOne region at a time; compare latency and CTR before/after

Metrics to Watch: suggest.e2e_p95{country}, snapshot.replication_lag{region}, override.replication_lag{region}, edge.hit_rate{region}.

Organizational Follow-up: Data-protection review of the EU pipeline; regional on-call coverage plan.

Ownership Question: "Who decides where query logs may be processed?" Staff answer: Legal and the privacy office decide; search infra implements and documents. It's a one-way door, so it gets a written decision, not a Slack thread.

Key Takeaway: "Immutable snapshots make serving trivially regional. The hard part is where the raw data may be processed."

What clears the Staff bar:

  • Separates serving locality from data-processing locality
  • Scopes overrides by country
  • Measures RTT before adding regions

9. Level Expectations Summary#

After studying this case study, you should be able to:

  • Distinguish query completion, entity typeahead and command completion, and pick one with reasons
  • Derive a per-keystroke latency budget and show where each millisecond goes
  • Explain why top-K per prefix is precomputed offline, and size the resulting index
  • Place caches at the client, the edge and the server, and explain how personalization affects cacheability
  • Design a trending overlay with diversity gates that resists brigading
  • Design the safety pipeline: k-anonymity, classifiers, overrides with an end-to-end propagation SLA
  • Choose replication over sharding when the index fits, and handle hot prefixes when it doesn't
  • Handle IME input, script-specific normalization and per-locale policy

The Bar for This Question#

Mid-level (L4): Builds a trie with frequencies, finds completions by traversal, maybe caches popular prefixes. Works for a demo; doesn't address latency at scale, freshness or safety.

Senior (L5): Precomputes top-K per node, serves from memory, shards and replicates, adds a cache, rebuilds periodically. Solid and correct. Tends to treat freshness as "rebuild faster", safety as a word list, and caching as one Redis tier; doesn't price personalization.

Staff+ (L6): Starts from intent and a per-keystroke budget. Moves all ranking offline into a pipeline with privacy and policy stages, publishes immutable versioned snapshots with staged rollout, layers caching from client to edge, adds a gated trending overlay, and owns an override path with a measured propagation SLA across nodes, edge and clients. Names owners: search infra for mechanism, T&S for policy, legal for removals. Handles non-Latin input. The interviewer should learn something from the answer.


10. Staff Insiders: Controversial Opinions#

10.1 "The Trie Is Not the Interview"#

EvidenceDetail
Serving logicHash lookup + filter; a few hundred lines
Where incidents come fromBad snapshots, removals, brigades, cache stampedes, i18n bugs
Where cost comes fromRequest volume, not index structure

The Staff position: Spend 2 minutes on the data structure — "precomputed top-K per prefix, FST or hash table, immutable snapshot" — and 40 on budget, caching, freshness and safety.

Why this matters in interviews: Interviewers who've asked this question many times have seen every trie. Moving past it quickly signals you know where the work is.

10.2 "Real-Time Suggestions Are a Liability, Not a Feature"#

EvidenceDetail
Abuse pathFastest path from searches to suggestions is the brigade path
User expectationMinutes-fresh trending meets breaking-news needs
ReviewSeconds-fresh leaves no time for any check

The Staff position: Minutes, gated, with an overlay that expires. Seconds-fresh suggestions for open-ended queries create more incidents than value.

Why this matters in interviews: Pushing back on "real-time" with a reasoned safety argument is a classic Staff-level signal.

10.3 "Replicate the Whole Index — Sharding Is Usually Premature"#

EvidenceDetail
Index size20–50 GB per large locale after filtering and pruning
Node RAM64–256 GB is routine
Sharding costRouting tier, hot shards, rebalancing, partial-failure modes

The Staff position: Replicate until the table doesn't fit, then shard only the long tail.

Why this matters in interviews: "Shard by first letter" is the most common L5 reflex; arithmetic that shows it's unnecessary is memorable.

10.4 "The Best Personalization Happens on the Client"#

EvidenceDetail
User historyAlready on the device
CacheabilityServer personalization makes responses private
PrivacyLess server-side storage of personal history

The Staff position: Merge recent history on the client; reserve server personalization for cases with measured lift.

Why this matters in interviews: It shows you see caching, cost and privacy as one decision.

10.5 "Suggestion Safety Is a Staffing Decision"#

EvidenceDetail
Removal SLAOnly as fast as the human who decides
ClassifiersNeed per-locale native review
Legal removalsRequire a policy owner, not an engineer

The Staff position: The override mechanism is easy; a 30-minute removal SLA needs 24/7 T&S coverage and per-locale reviewers. Say so.

Why this matters in interviews: Naming the people cost of a safety requirement is a bridge into the L7 conversation.


11. The Principal Lens (L7)#

Why L7 Sees This Problem Differently#

A Staff engineer designs an autocomplete. A Principal engineer notices the company has six of them — web search, maps, shopping, help center, the mobile app's search, and an internal tool someone open-sourced — each with its own blocklist, its own privacy threshold (or none), and its own idea of how fast a removal happens. The next suggestion that makes the news will come from whichever is weakest. At org scale, typeahead is a publishing platform: a shared build pipeline with mandatory privacy and policy stages, one override service with an audited removal SLA, and surface-owned ranking on top. The lookup service is the least interesting part.

🧭 Principal Move: "Before designing another suggester, I'd like to know how many exist and how each handles a removal request. If a court order today means six tickets to six teams, the design we need is one override service and one policy floor — and this surface is its first customer."

The Org-Level Fault Line#

One suggestion platform vs per-surface suggesters.

OptionWhat WorksWhat BreaksWho Pays
Per-surface suggestersEach team tunes for its domain; fast iterationN blocklists, N privacy thresholds, N removal paths; weakest one sets the riskT&S and legal (N integrations), PR (incidents)
One central suggester for everythingConsistent safetyCentral team bottleneck for every surface's ranking changesProduct velocity
Shared platform: mandatory safety + build, surface-owned ranking (L7 default)One removal path and policy floor; surfaces own scoring, candidates, UI2–4 engineer-quarters to build; requires a surface contractPlatform budget up front; pays back at the second surface

The deciding question: how many surfaces show suggestions to the public? One: build it well. Two or more: the safety floor must be shared; ranking can stay local.

Cost Model#

Assumptions: cloud list prices, memory-optimized nodes ~$1–2K/month (128–256 GB RAM), CDN requests ~$0.50–$1 per million, loaded engineer cost ~$25K/month.

ScaleArchitectureInfra $/monthHeadcountOn-Call Load
~1K QPS, 1 localeSearch engine completion suggester or small service; daily build; blocklist~$1–3K~0.5 FTEShared with search; rare pages
~100K QPS, 10 locales, 2 regionsDedicated lookup service (replicated), edge caching, daily build + trending overlay, override service~$25–60K (edge ~40%)3–5 FTE + T&S reviewersOwn rotation; ~2–4 pages/month
~1M QPS, 40+ locales, 5+ regions, multiple surfacesSuggestion platform: shared build, regional pools, client deny-sets, per-surface plug-ins~$200–500K10–15 FTE platform + per-surface + 24/7 T&SPer-region coverage; quarterly removal drills

The line that matters to leadership: at 1M QPS peak, every 10 points of edge hit rate is roughly the cost of a regional serving pool. And the dominant non-infra cost is people: a 30-minute, 24/7 removal SLA across 40 locales is a T&S staffing commitment larger than the serving team.

The 3-Year Evolution Path#

Diagram: The 3-Year Evolution Path

Each step is triggered by an event. Building a multi-surface platform before a second surface exists is how a team spends a year on abstractions nobody uses.

One-Way Doors vs Two-Way Doors#

DecisionDoorReversal CostWhy
Data structure (FST vs hash table)Two-wayDaysHidden behind the snapshot format
Replicate vs shardTwo-wayWeeksRouting layer can be added later
Edge TTLs and debounceTwo-wayMinutes (config)Should be remote-configurable
Client caching behavior in shipped app versionsOne-way-ishMonths (app update adoption)Old app versions cache for as long as they were told to
k-anonymity thresholdOne-way in the loosening directionCan't un-publish what leakedLowering it exposes private queries permanently
Where query logs are processedOne-wayLegal and regulatoryData-residency commitments
Public suggestion policyOne-way-ishRegulatory and press scrutinyOnce published, changes are visible and judged

🧭 Principal Insight: Spend review time proportional to reversal cost. The FST debate gets an hour. Client cache TTLs in the mobile app, the privacy threshold and the log-processing location get a review with privacy, legal and mobile in the room — because those are the ones you can't take back quickly.

The Standard I'd Write#

RFC: Public Suggestion Surfaces Standard (v1)

Scope: Any product surface that shows auto-generated suggestions, completions or related searches to users.

Requirements:

  • Suggestions derived from user behavior MUST pass a k-anonymity threshold of ≥ k distinct users over ≥ d days (values set by the privacy office per surface).
  • Every suggester MUST apply the shared override service on every response and MUST meet the removal propagation SLA (p99 ≤ 10 minutes to serving, edge and supported clients).
  • Client caches of suggestions MUST NOT exceed 1 hour TTL and MUST apply the client deny-set.
  • Index builds MUST record the policy version and pass golden-set validation before rollout; rollouts MUST be staged.
  • Surfaces SHOULD use the shared build pipeline; surfaces that do not MUST document equivalent privacy and policy stages.

Exceptions: Filed with the search platform architecture group and T&S; decision within 5 business days; maximum 2 quarters.

Success metrics: All public suggesters on the override service within 3 quarters; removal p99 ≤ 10 min measured by quarterly drill; zero suggestions shown below the privacy threshold; number of independent blocklists reduced to one floor plus surface rules.

What I'd Tell the VP#

"Search suggestions are the highest-traffic thing our search product does, and every one of them is something we say to users before they've asked. Today six products generate suggestions with six different safety processes, and the last removal request took six hours. I'm proposing one shared suggestion platform with a common safety floor and a 10-minute removal guarantee, while each product keeps control of its own ranking. It costs about four engineers for a year plus round-the-clock trust-and-safety coverage we partly have already. The return is that the next problem suggestion is gone in minutes instead of in the news, and new products get autocomplete in weeks instead of building their own."

Principal Interview Signals#

SignalWhat It Sounds Like
Inventories before designing"How many suggesters exist today, and how does each handle a court-ordered removal?"
Prices the keystroke"Each 10 points of edge hit rate is a regional pool. I'd spend on the edge before the origin."
Separates policy from mechanism"T&S decides what's removed; the platform guarantees it's gone in 10 minutes everywhere."
Names one-way doors"Client cache TTLs ship in app binaries. That's the decision I want reviewed, not the trie."
Treats safety as staffing"A 30-minute 24/7 removal SLA in 40 locales is a headcount plan, not a feature."

Staff answers that L7 interviewers find insufficient:

  • "We'll add an override layer for removals." — correct, but silent on the clients and other surfaces that don't use it.
  • "Trending is gated on distinct users." — right mechanism, but no owner for the thresholds and no per-locale review capacity.
  • "We'll replicate the index to every region." — sound serving design that never asks where the query logs may legally be processed.

Appendices

Appendix A: Data Structures and the Build Pipeline

A.1 The Build Pipeline#

Diagram: A.1 The Build Pipeline

A.2 Top-K Construction (pseudocode)#

for query q in suggestible_queries(locale):
    s = score(q)                                  # decayed count + CTR term
    for i in 1..min(len(q), L):
        p = q[0:i]
        heap[p].push((s, q)) keep top N           # bounded heap per prefix

for p in heap:
    emit(p, sorted(heap[p]))                      # sorted key order for FST

In practice this is a distributed job: emit (prefix, score, query) pairs, group by prefix, keep top N per group. Output is sorted by prefix and written as an FST or a sorted, front-coded key-value file.

A.3 Structure Comparison#

StructureMemory (50M queries)LookupUpdate ModelNotes
Pointer trie + top-K per node~100–300 GBO(L)Rebuild or locked mutationPointer overhead dominates
Hash map prefix → top-K~40–100 GBO(1)RebuildSimplest; easy to shard
FST with outputs~10–40 GBO(L)Rebuild onlyMost compact; used by Lucene
Sorted array + binary search~30–60 GBO(log n)RebuildMemory-map friendly

A.4 Long-Prefix Fallback#

For prefixes longer than L: look up the length-L prefix, filter its N candidates by the full prefix. If none match (rare, <2%), query a long-tail index (e.g., a search engine with edge n-grams over suggestible queries) with a 30–50ms budget; if it times out, return empty — an empty list is better than a late one.

Appendix B: Ranking Signals and Freshness

B.1 Signals#

SignalWhere ComputedTypical Weight Role
Decayed query frequencyBuildPrimary popularity
Suggestion acceptance rate (CTR)BuildCorrects popularity for usefulness
Downstream success (search clicked a result)BuildPenalizes queries that lead nowhere
Freshness / trending ratioStream overlayBoosts spiking queries
Locale and countryBuild (separate indexes) or cache keyRelevance
User's recent queriesClient or serve-timePersonalization
Policy demotionBuild + overrideSafety

B.2 Decay#

score(q) = Σ_d count(q, d) × 0.5^((today - d) / half_life)
half_life: 7 days (general), 1–2 days (news), 30 days (stable catalogs)

B.3 Serve-Time Merge#

Diagram: B.3 Serve-Time Merge
Appendix C: Snapshot Lifecycle and Rollout

C.1 Snapshot States#

Diagram: C.1 Snapshot States

C.2 Rollout Gates#

StageDurationGate
Single node10 minNo errors, memory within bounds
5%1 hourempty_response_rate within 10% of control; CTR within 2%
50%1 hourSame
100%—Previous snapshot kept hot for instant rollback

Nodes keep the previous snapshot memory-mapped so rollback is a pointer swap measured in milliseconds.

Appendix D: Caching, Client Behavior and the API Contract

D.1 Client Rules#

RuleValueWhy
Debounce50–100msDrops intermediate keystrokes for fast typists
Cancel in-flight on new keystrokeAlwaysPrevents out-of-order rendering
Ignore stale responsesCompare request sequence numberA slow response for 'ho' must not overwrite 'how'
Session cacheBy (locale, prefix)Backspace and retype are free
Prefix subsumptionIf result count < K, filter locallyAvoids requests for longer prefixes
Cache TTL≤ 1 hourRemovals must reach clients
Deny-setRefreshed with remote configFilter cached responses after removals

D.2 Edge Rules#

RuleValue
Cache keylocale + normalized prefix + client class
TTL5–15 min (≤ 3 chars: up to 30 min)
Personalized responsesCache-Control: private, bypass edge
Request coalescingOn
Safety purgePush-replace with filtered response, not delete

D.3 Caching Decision Tree#

Diagram: D.3 Caching Decision Tree

See CDN & Edge Caching and Caching for the general mechanics.

Appendix E: Safety, Privacy and Overrides

E.1 Defense Layers#

LayerMechanismLatency to EffectOwner
Privacy threshold≥ k distinct users over d daysNext buildPrivacy office sets k; infra enforces
Build-time policyBlocklists, classifiers per localeNext buildT&S
Trending gatesDiversity, reputation, classifiersMinutesT&S thresholds; infra mechanism
Serving overrideDeny exact / pair / entity / pattern< 2 min to nodesT&S operates; infra owns SLA
EdgePush-replace affected prefixes< 5 minInfra + CDN team
ClientDeny-set via remote config< 10 min for active clientsInfra + mobile

E.2 Override Matching#

Match TypeExampleUse
Exact completion"<string>" anywhereClear policy violations
Prefix + completion pairprefix "<name> is" → completion "<term>"Context-specific harms
EntityAny completion about entity XLegal removals about a person
PatternRegex/classifier classEmergent abuse patterns

E.3 Audit#

Every override records: who, when, match, reason code, ticket, scope (global / country), expiry. Overrides without expiry require T&S lead approval. Quarterly review removes stale overrides that no longer match anything.

Appendix F: Observability

F.1 Core Metrics#

# Latency and load
suggest.e2e_latency_p95{country}        # client-measured, keystroke to render
suggest.p99{stage}                      # lookup, merge, override, serialize
edge.hit_rate{region}
edge.origin_requests

# Quality
suggest.ctr{locale,surface}
suggest.keystrokes_saved{locale}
suggest.empty_response_rate{locale}

# Freshness
index.age_hours{locale}
trending.overlay_age_seconds
trending.rejected_by_gate{reason}

# Safety
override.propagation_seconds{layer}
tns.report_to_removal_minutes
synthetic_probe.violations_total        # must be zero

F.2 Critical Alerts#

AlertConditionSeverity
Latencysuggest.p99 > 25ms for 5 minPage
Empty responses> 2× baseline for a localePage
Stale indexindex.age_hours > 36Page
Removal propagationSynthetic probe finds removed suggestion after 10 minPage (high severity)
Edge hit-rate drop< 70% of baseline for 10 minWarn
Appendix G: Scale Evolution

G.1 What Works at Each Scale#

ScaleDesignWhat Breaks Next
< 100K items, any QPSShip list to clientCorpus growth
< 10K QPSSearch engine completion suggesterSafety pipeline, freshness control
10K–300K QPSDedicated replicated lookup, edge caching, daily buildRemovals, trending
> 300K QPS, many localesPlatform with overlay, overrides, client deny-sets, regional poolsMulti-surface governance

G.2 What You Don't Build on Day One#

  • Real-time mutable index (batch + overlay is enough)
  • Server-side personalization (start on the client)
  • Sharding (replicate until it doesn't fit)
  • Typo tolerance for short prefixes (only for ≥ 4 chars on miss)
  • Per-surface ranking plug-in framework (until there's a second surface)
  1. Loading the index…