Design a web search engine
Someone types three words, and within a few hundred milliseconds sees the ten best pages out of billions, with a sensible snippet for each. The crawler that fetches pages is Design D09 and the suggestions while typing are D10; this design is the part in between: building the index, answering queries across thousands of machines, and ranking well.
1. The prompt
"Design web search: given a query, return the most relevant pages from a crawl of the web, fast, with fresh results when news breaks."
What makes it hard:
- The index is far too big for one machine, so every query becomes a fan-out to thousands of them, and the slowest one sets the response time.
- Relevance is the product. Fast but wrong results are worthless, and "right" can only be measured, not proved.
- The web changes constantly, but rebuilding an index of billions of pages takes hours.
2. Frame the problem
How many pages do we search?
About 10 billion indexed pages, from a much larger crawl.
Billions of documents means sharding the index across thousands of machines; this single number drives the architecture.
How many queries?
Around 100,000 queries a second at peak.
Every query fans out to every shard unless we're clever, so shard replicas and caching become essential.
How fast?
Results within 300 ms at p99.
With a fan-out to thousands of shards, each shard must answer far faster than that, and the slowest shards must not hold everyone up.
How fresh?
Breaking news within minutes; popular pages within a day; the long tail within weeks.
Different freshness for different content suggests two indexes: a big batch-built one and a small real-time one.
Is crawling part of this?
No, assume a crawler (Design D09) delivers fetched pages.
It keeps the scope on indexing, serving and ranking.
Personalised results? Ads? Images?
Light personalisation (language, country). No ads or image search for now.
Personalisation by locale is cheap; per-user ranking is a much bigger system.
Spelling correction and suggestions?
Correct obvious typos; suggestions are D10.
A small query-understanding step before retrieval.
How is "good" measured?
Human-rated relevance on sample queries, plus click behaviour.
Ranking changes need offline evaluation and A/B tests, which shape the deploy process.
3. What it must do
- A user can search with a text query and get the ten most relevant pages, with a title and snippet each, and more pages on request.
- The system can index new and changed pages from the crawler, with freshness depending on the kind of page.
- The system can remove a page from results quickly (legal requests, malware, the page is gone).
Not now: crawling (D09), suggestions while typing (D10), ads, image and video search, per-user ranking.
4. How well it must do it
| Target | Why this number |
|---|---|
| Latency: p99 under 300 ms end to end speed | Search is used dozens of times a day; slow results feel broken. |
| Relevance: ranking quality measured offline and online, never worse after a change data quality | Relevance is the product; every change is a risk to it. |
| Freshness: news searchable within 10 minutes, popular pages within a day load shape | Breaking-news queries are exactly when people search most. |
| Availability: 99.99%, degrading gracefully (partial results beat none) surviving failures | With thousands of machines, some are always down. |
| Removals: a removed page disappears from results within minutes rules and compliance | Legal takedowns and malware can't wait for the next index build. |
5. Numbers that change the design
| Quantity | Working | → so we… |
|---|---|---|
| Text to index | 10 B pages × ~20 KB of text ≈ 200 TB; the compressed index is a sizeable fraction of that | Far beyond one machine → so we shard: ~10 M pages per shard ≈ 1,000 shards |
| Fan-out | 100 K queries/s × 1,000 shards | 100 M shard lookups a second if every query hits every shard → so we cache results, add a smaller top tier, and replicate shards |
| Tail | If each shard is slow 1% of the time, a query touching 1,000 shards is almost always slow (Module 02) | → so we hedge requests and accept partial results |
| Query repetition | Popular queries repeat heavily; a large share of traffic is a small set of queries | → so a result cache in front absorbs a big share of load |
Skip estimating snippet storage: documents live in a separate store and are fetched only for the final ten results.
6. The nouns
- Document: a page's URL, title, text, language and quality signals.
- Term and posting list: for each word, the documents containing it, with counts and positions.
- Index shard and its replicas.
- Query and result page.
- Blocklist entry: a URL that must not be shown.
documents doc_id, url, title, text_key (blob), lang, quality, indexed_at (document store, sharded by doc_id) postings term → [(doc_id, term_freq, positions)] sorted by doc_id, compressed (inside each index shard) doc_stats doc_id → length, static_score (per shard, for ranking) blocklist url_hash, reason, added_at (small, replicated to every front end)
7. The contract
GET /v1/search?q=hiking+boots&lang=en&page_token=…
→ {results: [{url, title, snippet}], next_page_token, corrected_query?}
internal Shard.Search(query_terms, k, deadline) → [(doc_id, score)] (gRPC, one call per shard)
Docs.Snippets(doc_ids, query_terms) → [{url, title, snippet}]
Indexer consumes crawled pages from a queue; RealtimeIndexer consumes a stream of news pages
Data flow: crawled page → parse and clean → tokenise (lower-case, split, stem) → write postings into index segments → publish segments to shards → serve.
8. First working version
One machine: an indexer, an inverted index, a document store, and a query service.
8.1 Indexing a page
The page's text is split into terms; each term's posting list gains this document. Lists are kept sorted by document ID so they compress well and can be intersected quickly.
8.2 Answering a query
Look up each query term's posting list, intersect them (or union, scoring partial matches lower), score candidates with a text-relevance formula such as BM25 (rare terms count more; a term repeated in a short document counts more), keep the top 10, then fetch titles and snippets.
9. Make it hold up
9.1 Splitting the index latency · availability
| Approach | Why | |
|---|---|---|
| ✗ Bad | One giant index on the biggest machine | 200 TB of text won't fit, and one machine can't serve 100 K queries a second. |
| ~ Good | Partition by term: each shard owns some words' posting lists | A query touches only its terms' shards, but multi-word queries must join huge lists across machines, and common words make hot shards. |
| ✓ Great | Partition by document (~1,000 shards, each a full index of its pages), with replicas per shard and a small top tier of the highest-quality pages | Each shard answers independently; most queries are satisfied by the top tier; replicas add throughput and survive failures. |
This is Module 12's scatter-gather, at web scale: a root server sends the query to one replica of every shard, each returns its top results, and the root merges them.
9.2 Fast posting lists latency
- Compression: store gaps between sorted document IDs, not the IDs, with variable-length encoding; small gaps take a byte or less, so more of the index fits in memory.
- Skip pointers let an intersection jump over long runs of a common word's list.
- Early termination: order each list so high-impact documents come first, or use bounds on the best possible score (WAND-style), and stop once the top 10 can't change.
9.3 Ranking in stages relevance
| Approach | Why | |
|---|---|---|
| ✗ Bad | Text match alone (BM25) | Easy to game with repeated keywords; ignores page quality and what users actually choose. |
| ~ Good | Text match combined with a static quality score (link authority, spam signals) | Much better, cheap to compute per document; still blind to intent and freshness. |
| ✓ Great | A cascade: cheap scoring retrieves ~1,000 candidates per query; a learned ranking model (click signals, freshness, quality, text features) re-ranks them; an expensive model (often a neural re-ranker) orders the final few dozen | Spend compute only where it changes the answer. Every model change is checked offline against human relevance ratings, then A/B tested. |
9.4 Fresh results without rebuilding everything freshness
- The main index is rebuilt in batches (new segments daily, full rebuilds less often), like an LSM tree's large files (Module 12).
- A small real-time index takes news and frequently changing pages from a stream within minutes.
- Every query searches both and merges, preferring the newer copy of a document.
- New segments are built offline, checked (size, sample queries), then rolled to shard replicas one at a time.
9.5 Taming the fan-out's tail p99 under 300 ms
| Approach | Why | |
|---|---|---|
| ✗ Bad | Wait for every shard | With 1,000 shards, one slow shard per query is nearly certain: every query is slow. |
| ~ Good | Replicas, and a deadline per shard | Fewer slow responses, but a deadline alone means missing results. |
| ✓ Great | Result cache for repeated queries, the top tier first, hedged requests (ask a second replica if the first is slow), and partial results when a few low-tier shards miss the deadline | Keeps p99 low with a small loss in completeness users rarely notice ("The Tail at Scale"). |
9.6 Removing a page fast removals within minutes
Rebuilding the index for one URL is far too slow. Keep a small blocklist replicated to every front end and filter results at serve time; the next index build drops the page permanently. The result cache is invalidated for affected queries, or simply filtered on the way out.
9.7 The whole design after the deep dives
10. Interviewer follow-ups
Ten times the queries.
Add replicas per shard (queries scale with replicas, not shards) and more front ends; the result cache absorbs repeated queries. The index size is unchanged, so no re-sharding is needed.
Ten times the pages.
More shards, and the fan-out grows, so lean harder on tiering: a top tier answers most queries, and deeper tiers are searched only when it doesn't return enough good results.
A whole shard's replicas are down.
Return results without that slice of the web; most queries won't notice, especially if it's a low tier. Alert, and rebuild the shard from the stored segments on new machines.
Spammers stuff pages with keywords.
Text match is only the first stage: quality scores, link signals, spam classifiers and user behaviour demote such pages. Spam detection runs in the indexing pipeline and feeds the static score.
Add semantic (meaning-based) search.
Add an embedding for each document and an approximate nearest-neighbour index (Module 29), retrieve candidates from both it and the keyword index, and let the ranking cascade combine them (hybrid search).
Personalise results.
Keep the index global; apply personal signals only in the re-ranking stage, using features like language, location and recent history, within the privacy rules.
A court orders a URL removed in one country.
The blocklist entry carries the countries; front ends filter by the user's country. Record the request for audit.
How do you ship a ranking change safely?
Offline: compare against human relevance ratings on thousands of queries. Online: an A/B test on a small share of traffic, watching clicks, reformulations and abandonment. Roll out gradually; keep the old model ready.
Make it cheaper.
More caching, a smaller top tier serving more queries, tighter compression, cheaper first-stage scoring, and running the expensive models on fewer candidates.
Searches spike when big news breaks.
The real-time index is small and replicated widely; the result cache catches the many identical queries; the ranking boosts freshness for queries detected as news.
11. When things break
| Failure | What users see | How we detect it | What limits the damage |
|---|---|---|---|
| Slow shard replica | Nothing, ideally | Per-replica latency | Hedged requests; eject slow replicas |
| Bad index segment | Missing or odd results | Sample-query checks before rollout; result-count anomalies | Roll segments replica by replica; keep the previous version |
| Ranking model regression | Worse results | Click and abandonment metrics; offline evaluation | Gradual rollout; instant switch back |
| Real-time index lag | Stale news | Age of newest indexed document | Alert; the main index still serves |
| Result cache down | Slower results | Cache hit rate; shard load | Shards are sized to survive a cold cache briefly; shed deep tiers first |
| Document store slow | Results without snippets | Snippet latency | Return titles and URLs; fill snippets later |
12. Wrap-up and the clock
Trade-offs we made:
- Document partitioning (every query touches many shards) for independence and easy growth.
- Partial results under a deadline instead of always-complete answers.
- A ranking cascade: cheap first, expensive only for the top candidates.
- Two indexes (batch and real-time) instead of one that's always up to date.
- A serve-time blocklist for fast removals.
What we'd watch: p99 latency · share of queries with partial results · cache hit rate · real-time index lag · relevance metrics from clicks and ratings · per-shard load.
At 10×: more replicas for queries, more shards and tiers for pages, more caching.
A 60-minute interview
| 0–5 | Frame: corpus, queries, latency, freshness |
| 5–10 | Must do, how well, the index-size and fan-out numbers |
| 10–15 | Nouns, contract, data flow |
| 15–25 | First version: indexing and querying one machine |
| 25–50 | Deep dives: sharding (9.1), ranking (9.3), the tail (9.5) |
| 50–60 | Freshness or removals, follow-ups, wrap-up |
A 90-minute interview
| 0–8 | Frame, including how relevance is measured |
| 8–15 | Must do, how well, numbers |
| 15–22 | Nouns, contract, data flow |
| 22–35 | First working version |
| 35–75 | Deep dives 9.1–9.6 and the final picture |
| 75–85 | Follow-ups: semantic search, spam, shipping ranking changes |
| 85–90 | Trade-offs, metrics, 10× |
| Level | What a strong answer shows |
|---|---|
| Mid-level | An inverted index, sharding with scatter-gather, a result cache, BM25-style scoring, a batch indexing pipeline. |
| Senior | Document vs term partitioning with reasons, the fan-out tail and its fixes, a ranking cascade, a real-time index next to the batch one, serve-time removals. |
| Staff+ | Tiering and cost per query, evaluation methodology for ranking (offline ratings plus A/B), safe segment and model rollouts, hybrid keyword and semantic retrieval, spam as an adversarial problem. |
Common mistakes:
- "Use Elasticsearch" with no idea how it scales.
- Term partitioning without discussing multi-word queries.
- Waiting for every shard.
- Ranking by text match alone.
- Rebuilding the index to remove one page.
- No way to measure relevance.
13. Try it, check yourself, read more
Inverted index playground. Eight tiny "web pages". Type a query and see each term's posting list, the candidate pages, and their BM25 scores. Notice how a rare word counts more than a common one, and how a short page that repeats your word ranks above a long one. The page called "Boots" is keyword stuffing: it scores near the top on text match alone, which is why real ranking adds quality signals (9.3).
BM25 with k1 = 1.2 and b = 0.75; words are lower-cased and a few common endings are stripped. Real engines add positions (phrases), quality scores and learned re-ranking on top.
How is a web-scale index usually partitioned?
A query fans out to 1,000 shards. What keeps its p99 low?
How do news pages appear within minutes when the main index is rebuilt daily?
A page must disappear from results today. What's the fast path?
| Resource | Why |
|---|---|
| The Anatomy of a Large-Scale Hypertextual Web Search Engine (Brin and Page, 1998) | The original architecture: crawl, index, rank by links. |
| Challenges in Building Large-Scale Information Retrieval Systems (Jeff Dean, WSDM 2009) | How the serving system evolved: sharding, tiers, encoding, caching. |
| The Tail at Scale (Dean and Barroso) | Hedged requests and why fan-outs are slow. |
| Introduction to Information Retrieval (Manning, Raghavan, Schütze) | Inverted indexes, compression and scoring, free online. |
| Okapi BM25 and learning to rank | The first-stage formula and the learned re-ranking stage. |
| Near real-time search (Elastic docs) | How new documents become searchable within a second in Lucene-based engines. |