Design problems
Search & discoveryMediumSDE-1 25 min

Design search autocomplete (typeahead)

Asked at
AmazonFlipkartGoogleLinkedInMyntraZomato

The interviewer asks

“Design search autocomplete for a large app like Google or Flipkart: as the user types each character, show the top 10 suggested queries within about 100 ms. Suggestions should reflect popularity, recent trends and the user's own history.”

What a strong answer looks like

Autocomplete is a read-heavy, latency-critical lookup: prefix in, top-K suggestions out. A strong answer precomputes the answer for every prefix rather than searching at request time. Query logs flow through Kafka into a batch job that aggregates query counts with time decay, filters junk and offensive queries, and builds a trie (or a prefix → top-10 map) that is serialised and loaded into memory on a fleet of suggestion servers. Requests are debounced on the client, cached in the browser and CDN for short prefixes, and served from memory in a few milliseconds. A streaming path adds trending queries within minutes, and a light personalisation step blends in the user's recent searches. Deep dives: the data structure and its memory size, the build pipeline and freshness, sharding and caching, and quality (ranking, filtering, typos, Hinglish).

Concepts to mention

Trie / FST with precomputed top-K per nodeOffline build from query logs with time decayImmutable index snapshots and atomic swapStreaming trending overlayClient debounce and request cancellationCDN and browser caching of short prefixesSeparate global and personal suggestionsReplicas first, shard by prefix range or locale when neededBlocklist filtering at build and serve timeTransliteration and typo tolerance

1 Requirements

Functional

  • Return up to 10 suggestions for the typed prefix, ranked by popularity
  • Update with each keystroke; handle prefixes from 1 character
  • Reflect trending queries within ~5-15 minutes (e.g. a new phone launch, a cricket match)
  • Blend in the user's own recent searches
  • Filter offensive, illegal and spam suggestions; support manual blocklists
  • Support Indian-language and transliterated queries (Hinglish like 'kurti for women')
  • Light typo tolerance for longer prefixes

Non-functional

  • ~150k suggestion requests/s at peak
  • Server latency < 20 ms p99; end-to-end < 100 ms p95 on 4G
  • Availability 99.99%; autocomplete must fail silently (search box still works without it)
  • Popularity data can be up to a day stale; trends within ~15 minutes
  • Index rebuild must not cause latency spikes or downtime

Out of scope (say it out loud)

  • The full search results page and ranking
  • Spell correction of the final query
  • Voice search
  • Ads in suggestions

2 Estimates

Suggestion requests~50k/s avg, ~150k/s peak100M DAU × 10 searches/day × ~4 requests per search after debouncing = 4B/day ÷ 86,400 ≈ 46k/s; ×3 peak
Query log volume~1B queries/day, ~50 GB/day100M × 10 searches; ~50 bytes per log record (query, user, ts, locale)
Suggestion vocabulary~10-20M queriesKeep queries seen at least N times in the last 30 days; the long tail of unique queries is mostly noise
Prefix entries~100-200M prefixes~15M queries × ~25 chars, with heavy sharing of prefixes; cap prefix length at ~30 chars
Memory for precomputed top-10~15-30 GB per full replica~150M prefixes × (10 query ids × 4 bytes + key and overhead ~60-100 bytes) plus a ~1 GB query string table. Fits in RAM; can also shard by prefix
Cache effectiveness~60%+ of requests from CDN/browser1-3 character prefixes are a small set (a few thousand) and make up a large share of requests

3 API

EndpointWhat it does
GET/v1/suggest?q=iph&locale=en-IN&limit=10Global suggestions for a prefix; cacheable at CDN for short prefixes (TTL ~5-15 min)
GET/v1/suggest/personal?q=iphUser-specific recent searches matching the prefix (not cacheable at CDN); can be merged server-side instead
POST/v1/search/logLog the submitted query and whether a suggestion was clicked (feeds popularity and quality metrics)
POST/internal/v1/blocklistAdd a term or pattern to the suggestion blocklist; takes effect within a minute

4 Data model

query_logKafka → data lake (Parquet on S3)
query_normaliseduser_id (hashed)localetsclicked_suggestionresult_clicks

Raw source for batch aggregation; retained 30-90 days.

query_statsData lake tables
querylocaledecayed_scorecount_1dcount_7dcount_30dctr

Built daily (or hourly) by Spark; score = sum of counts with exponential time decay, adjusted by click-through.

prefix index snapshotS3 (versioned files)
serialised trie or prefix → [top 10 query ids]query string tableversion

Immutable snapshot per build; servers download and swap atomically.

in-memory indexSuggestion server RAM
compact trie / FSTor hash map prefix → top-K list

Read-only per version, so lookups need no locks.

trendingRedis
locale → sorted set of rising queries with scores; prefix buckets for the top few hundred trends

Updated every minute by a streaming job; merged at query time.

user recent searchesRedis / Cassandra
user_id → last 50 queries with timestamps

Small per user; prefix-matched in memory at request time.

5 Architecture

CLIENTSEDGESERVICESASYNCDATAGET /suggestmissrecent searchestrending mergesearch logsrising queriesarchivedaily logsindex snapshotload + swapApp / browser(debounce)CDN (short prefixes)API gatewayUser history serviceSuggestion serversTrending job (Flink)Kafka (query logs)Index builder (Spark)Redis (trends,recents)S3 (logs, snapshots)
ClientEdge / gatewayService / workerQueue / streamCacheDatabase / storageAI / modelThird partyrequestasync (event / queue)

Hover a box to highlight its connections.

6 Request flows

User types 'iph'

  1. 1The client waits ~100-150 ms after the last keystroke (debounce) and cancels in-flight requests for older prefixes.
  2. 2It checks its local cache: if it already has results for 'iph' from earlier in the session, it shows them immediately.
  3. 3Otherwise it calls GET /suggest?q=iph&locale=en-IN. The query is normalised (lowercase, trimmed, Unicode normalised) so 'IPh ' and 'iph' share a cache key.
  4. 4For a short, common prefix the CDN returns a cached response (TTL ~10 min).
  5. 5On a miss, the gateway routes to a suggestion server, which looks up 'iph' in its in-memory index and gets the precomputed top 10 (e.g. 'iphone 16', 'iphone 15', 'iphone cover').
  6. 6It merges in trending matches from Redis (e.g. 'iphone 17 launch' rising today) and, for logged-in users, matching recent searches, then reranks and filters with the blocklist.
  7. 7The response (~1 KB) returns in a few milliseconds of server time; the client renders, highlighting the typed prefix.

Daily index build

  1. 1Spark reads the last 30 days of query logs from S3.
  2. 2It normalises queries, removes bots and spam (very high rate from one user or IP), drops queries below a minimum count and those matching the blocklist or PII patterns (phone numbers, emails).
  3. 3It computes a decayed popularity score per query and locale, adjusted by how often users click results for it.
  4. 4It builds, for every prefix, the top 10 queries by score, and serialises a compact index (FST or trie with top-K at each node) plus a query string table.
  5. 5The snapshot is validated (size, sample lookups, golden tests, offensive term check) and published to S3 with a new version.
  6. 6Suggestion servers download it in the background, load it into memory, and atomically swap a pointer to the new version; servers are rolled one group at a time.

7 Deep dives

The data structure: precompute top-K per prefix

A naive trie lookup walks to the prefix node and then traverses the whole subtree to find the most popular completions. For a one-letter prefix that subtree has millions of entries, which is far too slow per keystroke. The standard fix is to precompute: at build time, store the top K (say 10, sometimes 20 to allow reranking) query ids at every node. A lookup then costs O(length of prefix) to find the node plus O(K) to read the answer, a few microseconds. The memory cost is K ids per node; cap prefix length (~30 characters) and only include queries above a popularity threshold to keep it to tens of GB. Two practical encodings: a hash map from prefix string to a top-K list (simple, slightly more memory), or a compact trie/FST (finite-state transducer, as used in Lucene's suggesters) which shares prefixes and suffixes and is much smaller. Because the index is rebuilt and loaded as a read-only snapshot, there is no locking and no in-place update; freshness comes from rebuilding and from the separate trending layer, not from mutating the trie per query.

Build pipeline, freshness and trends

Popularity changes slowly for most queries, so a daily (or hourly) batch rebuild is enough. The score should favour recent activity: sum daily counts with exponential decay (for example a half-life of 7 days), so last year's news fades. Use click-through as a quality signal: a query people type often but never click results for may be broken or spam. Filtering is part of the build, not an afterthought: remove bots, PII patterns, adult and hateful content, and legally sensitive terms, and keep a manual blocklist that is also applied at serve time so urgent removals work within a minute. For trends, a streaming job (Flink) counts queries in sliding windows (e.g. last 15 minutes) and compares them with the usual rate for that query; queries whose rate jumps (a new phone, a breaking news name, a match) are written to Redis with a score. At request time, the server merges trending matches for the prefix into the precomputed list. This two-layer design (stable batch index plus small real-time overlay) avoids rebuilding the big index every minute.

Serving: caching, sharding and replicas

The cheapest request is the one never sent. Debounce on the client (~100-150 ms), reuse results within a session (typing 'iph' after deleting back from 'ipho'), and cancel stale requests. Short prefixes are a small set but a large share of traffic, and their answers are the same for everyone, so cache them at the CDN with a TTL of minutes; vary only by locale. Personal suggestions are fetched separately or merged server-side so they never pollute shared caches. Behind the CDN, the whole index (tens of GB) fits on one large-memory server, so the simplest scaling is full replicas behind a load balancer: add replicas for QPS, place them in each region for latency. If the index outgrows a server (many languages, many markets), shard by prefix range or by locale; sharding by first character is skewed ('s' and 'a' are huge), so use ranges chosen from the data or a hash of the first 2-3 characters. Each shard has several replicas. Index loads happen in the background with the old version serving until the swap, and only part of the fleet reloads at a time.

Quality: ranking, personalisation and Indian languages

The precomputed list is a candidate set; final ranking blends signals: global decayed popularity, trending boost, the user's own recent searches (strong boost when they match), and context such as the current category page on a shopping app. Keep personalisation light and fast: a user's last 50 queries can be prefix-matched in memory in microseconds. Indian users type in many ways: Hinglish in Latin script ('saree under 500'), Devanagari ('साड़ी'), and inconsistent transliterations ('kurta', 'kurtha'). Handle this by normalising scripts, adding transliteration variants as aliases in the index so a Latin-script prefix can match a Hindi query, and mining query reformulations from logs ('kurtha' followed by 'kurta' means they are the same intent). For typos, exact prefix matching is best for the first 3-4 characters; for longer prefixes, an edit-distance-1 lookup over the FST (Lucene's fuzzy suggester does this) recovers 'iphnoe' to 'iphone'. Measure success with suggestion click-through rate and keystrokes saved, not just latency.

8 Trade-offs

Precomputed top-K vs computing at query time

Precomputing makes lookups trivial and fast but uses more memory and makes updates a rebuild. Computing at query time (Elasticsearch completion or edge n-grams with sorting) is flexible but slower and costlier at 150k req/s. Precompute for the global list; compute small personal and trending parts live.

Batch rebuild vs real-time updates of the trie

Updating the trie on every query needs locking and makes top-K maintenance complex. A batch snapshot plus a small trending overlay keeps the hot path read-only and still reflects trends within minutes.

CDN caching vs personalisation

Caching shared responses at the CDN removes most load, but personalised responses cannot be shared. Split global and personal suggestions, cache the global part, and merge on the client or in a thin server step.

Full replicas vs sharding

Full replicas are simplest and avoid fan-out when the index fits in RAM. Shard only when memory forces it, and choose prefix ranges based on traffic, not alphabet letters.

9 Bottlenecks

Traffic spikes from a big event

A launch or match sends many users typing the same few prefixes. CDN caching of short prefixes absorbs most of it; make sure the CDN TTL is not so long that trending suggestions are hidden (use a shorter TTL for trending-heavy locales during events).

Index size growth

More markets and languages grow the index past one machine. Shard by locale first (natural boundary), then by prefix range; or prune by a stricter popularity threshold.

Reload memory pressure

Loading a new 20 GB snapshot while the old one is serving needs double memory. Provision for it, or use memory-mapped files so the OS pages the new index in gradually.

10 Failure handling

Suggestion service down

The client times out after ~200 ms and shows no suggestions; search still works. Recent searches stored on the device can still be shown.

Bad index build (offensive or broken suggestions)

Validation gates block obviously bad builds; if one slips through, roll back to the previous snapshot version in minutes and apply the serve-time blocklist immediately.

Trending pipeline stops

Suggestions fall back to the batch index only; slightly stale but correct. Alert on trending job lag.

Redis unavailable

Skip the trending and personal merge with a tight timeout and serve the precomputed list.

11 What impresses

  • Explains why walking the subtree is too slow and stores top-K at each node
  • Estimates index memory and concludes it fits in RAM, so replicas beat early sharding
  • Separates a stable batch index from a real-time trending overlay
  • Uses CDN caching for short prefixes and keeps personalisation out of shared caches
  • Treats offensive-suggestion filtering as a first-class requirement with a fast kill switch
  • Handles Hinglish and script variants for Indian users

12 Mistakes

  • Running a LIKE 'prefix%' query on a SQL database per keystroke
  • Traversing the whole subtree at request time to find top results
  • Updating a shared trie in place on every search
  • Sending a request for every keystroke without debounce or caching
  • Sharding by first letter and creating huge hot shards
  • Forgetting to filter offensive or PII suggestions

13 Follow-ups

Q1How would you add suggestions that complete in the middle of a query (e.g. 'red ... shoes')?
Q2How would you make suggestions category-aware on a shopping app?
Q3How do you A/B test a new ranking for suggestions?
Q4How would you remove a suggestion globally within one minute for legal reasons?
Q5How would you support 10 Indian languages?
Q6How do you stop people from gaming suggestions with bots?

Saved on this device. Create a free account to keep it on every device.

Practise this with Sam

A real mock interview, out loud, with an honest scorecard. Free to start.

Start free