Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

System Design

Design a Search Engine

Step 1 / 9

Learn system design by building a web search engine step by step.

The numbers to beatBM25text relevancePageRankauthoritytop 10shown

The whole design, in writing

Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.

The big idea

What is a search engine?

Type a few words and get the ten best pages from billions, ranked by relevance, in a few hundred milliseconds. Two hard problems hide inside: finding the matching documents fast, and ordering them so the most useful is first.

Usertypes a query
New in this step: User.

Flip the data around with an inverted index (term → documents) so matching is a list lookup, not a scan. Then rank the matches by relevance and authority, shard the index for scale, and cache the popular queries.

What the new pieces do

Userclient
Wants the ten most relevant pages out of billions, in a few hundred milliseconds. Relevance and speed together are the whole product.

Step 1 · The skeleton

Query in, matches out

Given a query, you must return documents containing those words. Scanning every page for every query is hopeless at web scale — you’d need to read the entire internet on each search.

Query ServiceInverted Index
New in this step: Query Service, Inverted Index. · swipe to pan the diagram

Return docs containing the query words, from billions of pages, in ~100ms. How do you store the data?

  1. Reading the entire web on every search is hopeless — billions of docs per query. You must precompute where each word lives, not search at query time.

  2. Doc → words answers "what’s in this page?", the wrong question. You’d still scan every doc’s word list to find a term. You need the inverse.

  3. Term → docs turns a query into looking up each word’s postings list and intersecting them — a handful of list ops instead of scanning the web. The single most important idea here.

Stand up a Query Service backed by an Inverted Index: instead of “which words are in this doc?”, store “which docs contain this word?”. A query becomes looking up each term’s document list and intersecting them.

Why this piece earns its place

The decision this step really makes is what rides in the postings, not that you invert the data. Doc ids alone give the smallest possible index and answer “which pages contain this word” and nothing else. Add each term’s frequency and you can score how well a document matches, which is what the ranker later needs. Add positions and you can answer a phrase query, but positions are stored per occurrence rather than per document, so they usually become the single largest thing in the index. That is the knob. I would keep frequencies everywhere, because ordering is the product and you cannot add them retroactively without rebuilding, and treat positions as a per-field choice: carried for titles and body text where phrases matter, dropped for boilerplate and anchor fields where they do not. Going too lean is a ceiling you cannot lift later. Going too rich pushes the hot postings out of memory, and the moment a common term’s list has to be read off disk, the tidy story about intersecting a few lists quietly becomes a story about disk seeks.

What the new pieces do

Query Servicebackend
Receives the query, coordinates parsing, retrieval and ranking, and returns the results page. The conductor of the read path.
Inverted Indexindex
The core structure: for each term, the list of documents containing it (a postings list). Finding matches becomes intersecting a few lists, not scanning the web.

Back of the envelope

query = intersect postings lists
a few list ops, not a web scan
term → sorted doc-id list
precomputed once at index time
+ positions & frequencies
enable phrase match and relevance scoring

Step 2 · Match the right forms

Parse the query

A user types “Running Shoes” but the index stored “run” and “shoe”. Case, punctuation, stop words and word forms all cause misses unless the query is processed the same way the documents were.

Query ServiceQuery ParserInverted Index
New in this step: Query Parser. · swipe to pan the diagram

A user types "Running Shoes" but the index stored "run" and "shoe". How do you make them match?

  1. Enumerating every case/tense/plural for every word explodes the index and still misses forms you didn’t list. Normalize both sides instead of expanding one.

  2. Tokenize, lowercase, strip stop words and stem the query exactly as the documents were, so "Running" and "run" resolve to the same postings list. Symmetry of analysis is the fix.

  3. Substring matching can’t use the postings lists (slow) and is wrong (matches "run" inside "errand"). The principled fix is identical normalization, not fuzzy scanning.

A Query Parser tokenizes, lowercases, strips stop words and stems terms — applying the exact pipeline used at index time. Now “Running” and “run” resolve to the same postings list, so matching actually works.

Why this piece earns its place

Worth saying out loud what this pipeline destroys, because none of it is recoverable at query time. Stemming conflates words that are genuinely different — an aggressive stemmer will fold university and universe onto one root — and stop-word removal deletes exactly the queries that are made of stop words: “The Who”, “to be or not to be”. Both fail silently; the user gets results, just not theirs. So I would stem toward the milder end, and keep stop words in the index with their positions rather than dropping them, skipping them only when scoring, so a phrase query can still use them. The second consequence is operational. Symmetry of analysis means the analyzer is part of the index’s schema, not a service you redeploy independently: change the stemmer and every posting written under the old rules is unreachable by the new query forms, with nothing to show for it but a drop in matches. That makes an analyzer change a corpus-wide rebuild, so the analyzer carries a version and that version travels with the index built from it.

What the new pieces do

Query Parserservice
Lowercases, tokenizes, removes stop words and stems the query (“running” → “run”) so it matches the same forms used when the index was built.

Step 3 · Build the index

From pages to postings

The inverted index doesn’t appear by magic — it has to be built from the crawler’s billions of pages, and rebuilt as new pages arrive. Doing that inline with queries would be impossible.

Usertypes a queryCrawlerfetched pagesQuery Serviceparse + serveIndexerbuild the index
New in this step: Crawler, Indexer.

The inverted index must be built from billions of crawled pages and kept current. Where does that happen?

  1. Indexing a document is heavy work; doing it in the query path would make every search wait on parsing pages. Build and serve are opposite workloads — separate them.

  2. The indexer reads crawled docs, runs the same analysis, and writes postings in the background. Separating write-heavy indexing from read-heavy querying lets each scale independently.

  3. Rebuilding billions of postings for each new page is absurdly wasteful. Indexing is incremental — append/merge new postings into segments — not a full rebuild per change.

An offline Indexer pipeline reads crawled documents, runs the same analysis, and writes each term’s postings (doc ids, positions, frequencies) into the index. It runs continuously in the background, separate from serving queries.

Why this piece earns its place

Separating build from serve buys the two scaling stories, and it also introduces a queue between the crawler and the indexer that nobody draws at the whiteboard. That queue’s depth is the freshness of the product. The number I would put on a dashboard is not documents indexed per second, it is the age of the oldest thing still waiting — how long it takes a fetched page to become searchable. It matters because the failure here is never an outage. Indexing capacity that keeps up on average falls behind during a crawl burst and drains afterwards, and the only symptom anyone can see is that results are a little older than they were. Nothing errors, so nothing pages anyone. That also argues against draining the queue in arrival order. Some lag is unavoidable, so I would rather choose where it lands: a news page or a high-authority site jumps the queue while a rarely-changing page in the long tail waits, because the same hour of delay is worth wildly different amounts on those two documents.

What the new pieces do

Crawlersource
Supplies the raw documents to search over (see the web-crawler design). The index is only as fresh and complete as what the crawler delivers.
Indexerworker
Parses crawled pages, extracts terms, and writes postings into the inverted index — the offline pipeline that turns documents into searchable structure.

Step 4 · Which ten first?

Ranking

A common query matches millions of documents. Returning them in index order is useless — the user only looks at the first few, so the order is the entire value of the product.

Query Parsertokenize · normalizeRankerBM25 + PageRankInverted Indexterm → docsDoc Storetitles · snippets
New in this step: Ranker, Doc Store.

A common query matches millions of docs. Returning them in index order is useless. How do you order them?

  1. Raw term frequency rewards keyword-stuffed and long pages and ignores authority — a spam page repeating the word wins. You need match quality and trustworthiness.

  2. Authority without textual relevance returns famous pages that barely match the query. A trusted page on the wrong topic isn’t a good result — you need relevance too.

  3. BM25 asks "does this match the words well?", PageRank asks "is this page trustworthy?". Combining them (plus freshness, quality) is why search beats a keyword grep. The Doc Store then supplies titles/snippets for the winners.

A Ranker scores candidates by textual relevance (TF-IDF/BM25 — how well the terms fit the doc) and authority (PageRank — how important the page is), plus signals like freshness. The Doc Store then supplies titles and snippets for the winners.

Why this piece earns its place

Relevance scoring is not one formula, it is a formula with dials, and the interesting question is how you set them. BM25’s length normalization decides how hard a document is penalized for being long: turn it all the way up and a thorough, genuinely comprehensive page loses to a short one that happens to be on topic; turn it off and long pages win by accumulating terms, which is the stuffing failure we just rejected. Its saturation term decides when repeating a word stops helping, which is why a page that says the query one more time than its neighbour does not simply out-rank it. Neither of those, and neither the weight between relevance and authority, is something to argue about from first principles. I would set them by measurement: a held-out set of queries with human relevance judgments to catch regressions, and interleaving two rankers on live traffic to see which one people actually prefer. The design contribution here is making ranking a scored function with parameters you can move, so it becomes an experiment you run rather than an opinion you defend.

  • BM25text relevance
  • PageRankauthority
  • top 10shown

What the new pieces do

Rankerservice
Scores candidate documents by textual relevance (BM25/TF-IDF) and authority (PageRank), then orders them so the best result lands at the top.
Doc Storestore
Holds document metadata and content used to build the title, URL and snippet shown for each result after ranking selects the winners.

Step 5 · Too big for one box

Shard & scatter-gather

The index for the whole web is far too large for a single machine’s memory or disk, and one server can’t evaluate a query against billions of documents in time.

Query ServiceRetrieverIndex Shards
New in this step: Retriever. · swipe to pan the diagram

The whole-web index is far too big for one machine. How do you search billions of docs in time?

  1. Term-sharding creates hotspots on common words and uneven load, and the postings for a popular term still won’t fit one box. Document-sharding spreads work evenly.

  2. Each shard searches its slice in parallel; the retriever fans out (scatter), each returns its local top results, and they’re merged (gather). You wait for the slowest shard, not the sum — latency stays bounded as the corpus grows.

  3. Replicas add throughput but every replica still can’t fit or evaluate the whole-web index in time. You must partition the index across machines, then replicate each shard.

Shard the index by document across many machines. A Retriever sends the query to every shard in parallel (scatter), each returns its local top candidates, and the retriever gathers and merges them for ranking. Latency stays bounded as the corpus grows.

Why this piece earns its place

Scatter-gather quietly assumes each shard can pick its own best candidates alone, and the ranking from the previous step breaks that assumption. Inverse document frequency — the part that makes a rare word count for more than a common one — is a property of the whole corpus, not of a slice. If every shard computes it from the documents it happens to hold, the same document scores differently depending on which machine it landed on, and the merge is then comparing numbers that were never on the same scale. The rare, meaning-carrying terms are precisely where the slices disagree most, so the error lands on the queries that matter. Two defenses, and I would use both. Assign documents to shards by a hash rather than by host or crawl order, so each slice is a fair sample of the corpus and local statistics approximately agree. And compute term statistics globally on a slow cycle, then push that table to every shard so they all score against one view of the corpus. This is also the reason shard assignment is not a free choice.

  • by-documentsharding
  • parallelshard search
  • mergetop results

What the new pieces do

Retrieverservice
Fans a query out to every index shard in parallel, gathers each shard’s top candidates, and merges them — the pattern that makes billions of docs searchable.

Back of the envelope

shard by document
each shard searches a slice in parallel
latency = slowest shard
not the sum — bounded as the corpus grows
+ replicate each shard
adds throughput and fault tolerance

Step 6 · The same searches

Cache hot queries

A huge share of searches are the same popular queries repeated endlessly. Re-running the full parse → scatter-gather → rank pipeline for each identical query wastes enormous compute.

Query ServiceRetrieverRankerIndex ShardsIndexerResult Cache
New in this step: Result Cache. · swipe to pan the diagram

A huge share of searches are the same popular queries repeated endlessly. How do you save that compute?

  1. Scaling the fleet to re-run identical popular queries burns compute on work you already did. When the same input repeats, recompute is the waste to eliminate, not to scale.

  2. Postings caching helps retrieval but still re-runs parse → scatter-gather → rank → merge for every repeat of a head query. You can skip the whole pipeline by caching the finished result.

  3. Search traffic follows a steep power law, so a modest result cache answers a large fraction of queries in ~1ms, sparing the index and ranking fleet for the rare, novel long-tail queries.

Cache the result pages for popular queries. Search traffic follows a steep power law, so a modest Result Cache answers a large fraction of queries in a millisecond, sparing the index and ranking fleet for the long tail.

Why this piece earns its place

The thing to get right here is what the key actually is, because a result page is not a pure function of the query string. The same words asked from another country, in another language, or with different content settings should not always return the same page. Key on the query alone and the cache serves one region’s results to everyone, which looks like a ranking bug and is really a caching bug. Fold the individual user in and the skew that justified the cache evaporates, because the repetition lives in the part of the request that people share — every dimension added splits the head into smaller heads. So I would key on the query plus the small set of things that genuinely change results, and simply not cache anything personalized. The other half is expiry. An entry is a snapshot of an index that keeps moving underneath it, so its lifetime is set by the freshness promise rather than by the hit rate I would like to report: a head query is exactly the one where a newly indexed page most needs to show up.

What the new pieces do

Result Cachecache
Caches the result pages for popular queries. Search traffic is highly skewed, so a small cache serves a large share of queries instantly.

Back of the envelope

head queries ≫ tail
a steep power law in search traffic
small cache ⇒ big hit rate
a large share of queries answered in ~1ms
miss ⇒ full pipeline
spend retrieval budget on novel queries

Step 7 · Stay current

Freshness & the sharp edges

News breaks and pages change constantly, but rebuilding a web-scale index takes time. Users also expect spelling correction, synonyms, and protection from spam pages gaming the ranking.

UserCrawlerQuery ServiceQuery ParserRetrieverRankerIndex ShardsDoc StoreIndexerResult Cache
The system as it stands at this step. · swipe to pan the diagram

Run a small, fast fresh index for recent content merged with the big base index at query time. Layer query understanding (spell-correct, synonyms) before retrieval, and add spam/quality signals to ranking so manipulated pages don’t win.

Why this piece earns its place

The uncomfortable part of this layer is where its training data comes from. Spelling correction weighted by query logs, synonyms learned from click data, and the behaviour signals feeding quality scoring are all learned from what this engine already chose to show people. A document the ranker never surfaces earns no clicks, and that absence then confirms it should not be surfaced. The loop closes, and the system loses the ability to discover that it is wrong. Spam defences inherit the same blindness, because a page suppressed for a bad reason looks identical in the logs to one suppressed for a good one. So part of the design is deliberately not being greedy with the ranking: hold out a slice of traffic where the ordering is perturbed, and keep some exploration in the lower positions, so there is data about documents the current model does not favour. Alongside it, keep an evaluation set built from human judgments rather than from clicks, because it is the one measurement the feedback loop cannot quietly move.

You did it

You just designed a search engine.

UserCrawlerQuery ServiceQuery ParserRetrieverRankerIndex ShardsDoc StoreIndexerResult Cache
The finished design, end to end. · swipe to pan the diagram

Everything you assembled, in order

  • An inverted index turns search from a scan into term-list lookups.
  • Query parsing applies the same analysis as indexing so terms actually match.
  • An offline indexer builds postings from crawled pages, separate from serving.
  • Ranking combines text relevance (BM25) with authority (PageRank).
  • Document-sharding plus scatter-gather retrieval scales to billions of docs.
  • A result cache exploits skewed traffic to answer head queries instantly.
  • A fresh index, query understanding and spam signals keep results current and clean.

Where an interviewer pokes next

Getting the boxes right is the easy half. These are the questions that separate a candidate who drew the diagram from one who has run the thing. Answer each one out loud before you open it.

  1. How do you intersect huge postings lists fast for multi-word queries?

    Store postings sorted by doc id and intersect with skip pointers (galloping search), starting from the rarest term so the smallest list drives the walk. Add early termination (WAND / block-max) so you stop once no unseen doc can crack the top-k. You never fully materialize the intersection of common terms.

  2. How does the index update without rebuilding everything?

    Segment-based indexing (à la Lucene): new docs go into small immutable segments searched alongside the big ones and merged in the background; deletes are tombstones applied at query time. Near-real-time updates without rewriting the whole index — the freshness/scale split from step 7.

  3. How are spelling correction and synonyms handled?

    In a query-understanding layer before retrieval: a spell corrector (edit-distance against a term dictionary weighted by query logs) rewrites/suggests, and synonym expansion adds related terms (learned from click data). It’s kept separate from the index so you can iterate without re-indexing.

  4. Scatter-gather waits for the slowest shard — how do you tame tail latency?

    Replicate each shard and issue hedged requests to more than one replica, taking the first to answer; set a deadline after which you return partial results from the shards that responded. A few missing shards cost a little recall, not a hung query — graceful degradation is built in.

  5. How do you stop spam pages gaming the ranking?

    Ranking is an arms race: combine many quality/authority signals (PageRank, spam classifiers, site reputation, behavior signals) so no single stuffable factor dominates, keep the weights secret and frequently retrained, and run link-spam / content-farm detection offline to feed penalties into the ranker.

Check yourself — the answers, and why

Eight steps in, these are the calls you should be able to make cold. Pick one, then read why.

  1. The inverted index maps…

    Term→docs turns search into postings-list lookups instead of scanning every document.

  2. Queries use the same analysis as documents so that…

    Symmetry of analysis — mismatched normalization is the top cause of missed matches.

  3. Good ranking combines…

    BM25 = does it match the words; PageRank = is the page trustworthy. Together they beat keyword grep.

  4. Scatter-gather over a document-sharded index means latency is…

    All shards search in parallel; you wait for the slowest, so adding shards scales corpus, not latency.

  5. A result cache works because search traffic is…

    A small cache of popular query results answers a large share of traffic in ~1ms.

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

What it must do

Agree on these before drawing a single box.

  • Search: given a query, return the ten most relevant documents out of billions, in a few hundred ms.
  • Match: find documents containing the query terms via an inverted index — term → docs.
  • Understand the query: tokenize, lowercase, strip stop words and stem so “Running” matches the indexed “run”.
  • Rank: order matches by textual relevance (BM25) and authority (PageRank); show the top 10.
  • Stay fresh: newly crawled pages become searchable, with spelling correction and synonyms improving recall.

The qualities that shape everything

Each one names the mechanism that buys it.

Match without scanning the web
An inverted index maps each term to its postings list, so a query is a handful of list intersections instead of reading billions of docs.
Queries match the stored forms
The Query Parser runs the exact analysis used at index time — tokenize, lowercase, stop-word, stem — so “Running Shoes” resolves to the same postings as “run”/“shoe”.
Indexing never slows a query
An offline Indexer builds postings from crawled pages in the background, separate from the read path — build offline, serve online.
The best result lands first
A Ranker combines textual relevance (BM25) with page authority (PageRank) plus freshness — order is the whole product.
Search billions of docs in time
Shard the index by document and scatter-gather: fan the query to every shard in parallel, gather each shard’s top candidates, merge — latency is the slowest shard, not the sum.
Absorb the repeated head queries
A Result Cache exploits steeply skewed traffic — a small cache answers a large share of queries in ~1ms, sparing the retrieval fleet.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

Inverted index (term → docs) over a forward index (doc → words)

A forward index answers “what’s in this page?” — you’d still scan every doc’s word list to find a term. Inverting it turns a query into a few precomputed list lookups instead of reading the web.

Offline incremental indexer over building postings on the fly per query

Indexing a document is heavy; doing it in the query path makes every search wait on parsing pages. Build-heavy indexing and read-heavy querying are opposite workloads — separate them so each scales.

BM25 relevance × PageRank authority over raw term frequency alone

Sorting by how often a term appears rewards keyword-stuffed and long pages and ignores trust — a spam page repeating the word wins. Combining match quality with authority is why search beats a keyword grep.

Shard by document + scatter-gather over sharding the index by term

Term-sharding creates hotspots on common words and the postings for a popular term still won’t fit one box. Document-sharding spreads work evenly and you wait for the slowest shard, not the sum.

Cache whole result pages over scaling the retrieval fleet to re-run head queries

Re-running identical popular queries burns compute on work already done. Search traffic follows a steep power law, so a modest result cache answers a large share in ~1ms — spend retrieval budget on the novel long tail.

The answer, out loud

What a strong answer to “Design a Search Engine” sounds like, first question to last trade-off. It is about 7 minutes of talking; the whiteboard and the interviewer fill the rest of the 45. Read it aloud once, then close the page and give it yourself.

  1. 0–4 min

    What we are actually building

    I’ll scope it first. Someone types a few words and we return ten pages out of billions, best one first, in a few hundred milliseconds. Two genuinely different hard problems hide in that sentence — finding the documents that match at all, and deciding which of them is best — so I’ll build them separately. I’ll assume the documents already exist; going and getting them is a crawler, which is its own design. And I want to say up front that the ordering is the product here. Matching is table stakes.

  2. 4–9 min

    Flip the data around

    The skeleton is one service and one data structure. The naive version scans documents looking for the query words, which means reading the web on every search, so that’s off the table immediately. The fix is to flip the data: instead of storing which words are in each document, store, for each word, the list of documents that contain it. That’s an inverted index, and it turns a search into looking up a couple of lists and intersecting them. The important part is that the work is precomputed — it happens once, when we first see the document, not when someone asks.

    Built in step 1: Query in, matches out
  3. 9–14 min

    Both sides have to speak the same language

    Matching then fails in a boring way. Someone types Running Shoes, we stored run and shoe, and we return nothing. The reason is that we transformed the documents on the way in — lowercased them, dropped the words that carry no meaning, cut words back to a root — and we never transformed the query. So I’d put a query parser in front of retrieval running the identical pipeline. The rule is symmetry: whatever we do to a document at index time, we do to the query, step for step. Most of the why-didn’t-this-match bugs in a search system are those two pipelines drifting apart.

    Built in step 2: Parse the query
  4. 14–20 min

    Build it in the background

    Then, where does the index come from. Not the query path — parsing a page is heavy, and putting it in front of a search makes every user wait on it. So there’s an offline indexer that reads what the crawler fetched, runs the same analysis, and writes postings in the background. And it’s incremental: re-indexing the whole web because one page changed is absurd, so new documents go into new pieces of the index that get merged over time. A nice property of the split is that if the indexer stops, search keeps working. We just stop getting fresher.

    Built in step 3: From pages to postings
  5. 20–27 min

    Which ten go first

    Now the half that is actually the product. A common query matches millions of documents and nobody looks past the first few, so the ordering carries all the value. Sorting by how often the word appears rewards long and stuffed pages. Sorting by how important the page is returns famous pages that barely answer the question. So we score both: a textual relevance score for how well the terms fit this document, and an authority score derived from the link graph for whether the page is worth trusting, plus signals like freshness. A document store then supplies titles and snippets, but only for the ten that won.

    Built in step 4: Ranking
  6. 27–34 min

    Split the corpus, ask everyone at once

    Scale next. The whole-web index doesn’t fit on a machine, and one machine couldn’t evaluate billions of documents in time anyway. I’d split by document: each machine holds a slice of the corpus and the full index over that slice. A query goes to every shard at once, each returns its own best candidates, and the retriever merges them before ranking. What I care about is that latency is the slowest shard rather than the sum, so the corpus can grow without every search getting slower. I’d size the shard count for the memory footprint, then replicate each shard for throughput and for surviving a dead machine.

    Built in step 5: Shard & scatter-gather
  7. 34–39 min

    The same searches, over and over

    One more thing about the traffic. Searches are nowhere near uniformly distributed — a small number of queries get asked endlessly and the tail is nearly all unique. Re-running parse, scatter-gather and ranking for an identical popular query is pure waste, so I’d put a result cache in front and return the finished page for hot queries in about a millisecond. I’d be clear that it’s a speed and cost layer, not a source of truth. If it disappears, every answer is still correct, everything is just slower and more expensive — which is the right way round for a cache to fail.

    Built in step 6: Cache hot queries
  8. 39–43 min

    What the real world adds

    Then the messy parts. News breaks much faster than a web-scale index rebuilds, so I’d run a small fast index over the last few hours alongside the big one and merge them at query time. Users also expect their typos fixed and their synonyms understood, and that belongs in a query-understanding layer ahead of retrieval so we can iterate on it without touching the index at all. And ranking is adversarial — there is real money in being first — so spam and quality signals aren’t a later addition, they’re part of the ranker from the start.

    Built in step 7: Freshness & the sharp edges
  9. 43–45 min

    Where it trades off

    To close: invert the data, analyse both sides identically, build offline, rank on relevance and authority together, shard and scatter-gather, cache the head. The trade-off running through every stage is recall against latency — each stage throws candidates away to stay inside the time budget, and a document cut early can never be recovered by better ranking later. With more time I’d go properly into that first cut, how each shard picks its candidates cheaply before the expensive scoring runs, and into what we return when a shard misses its deadline.

What this teaches

Learn system design by building a web search engine step by step. An interactive guide covering the inverted index, query parsing, building the index from crawled pages, ranking with BM25 and PageRank, sharding with scatter-gather retrieval, result caching, and freshness.

Key takeaways

  • An inverted index turns search from a scan into term-list lookups.
  • Query parsing applies the same analysis as indexing so terms actually match.
  • An offline indexer builds postings from crawled pages, separate from serving.
  • Ranking combines text relevance (BM25) with authority (PageRank).
  • Document-sharding plus scatter-gather retrieval scales to billions of docs.
  • A result cache exploits skewed traffic to answer head queries instantly.
  • A fresh index, query understanding and spam signals keep results current and clean.

Concepts covered

  • What is a search engine?
  • Query in, matches out
  • Parse the query
  • From pages to postings
  • Ranking
  • Shard & scatter-gather
  • Cache hot queries
  • Freshness & the sharp edges
built to be ranked, not memorized — make the calls, drop the cache, run the gauntlet.
Finished this one? 0 / 65 System Designs done
NextDesign YouTube Next in “System Design in 30 Days” · step 18 of 21

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More System Designs