~learn.
?

Side quest 05Web search

Google Search.

The World's Most Complex Information Retrieval System

INTERMEDIATE31 MIN READ6 PHASES · 45 MIN INTERVIEW+120 XP

Mission brief

Design a web search engine

Quest log0/7 stages · 0/5 checkpoints · 0/7 quick checks
searches a day (2025 estimate, from Google's 5T+ a year)
~14B
pages to index (design target)
50B → 500B
queries per second at peak (design target)
100K
p99 query latency (design target)
< 500 ms

Company names and logos are trademarks of their owners, shown only to identify each company. This course isn't affiliated with or endorsed by any of them.

watch it first · newThe whole design in a 13-minute animated lecture4 quiz checkpoints · ask the AI about any moment · captions, chapters and speed
Optional · timed run · 6 phases · 45 min

Want it harder? Hide the model answer and work each phase in a scratchpad against its time budget, then reveal and score yourself. Or simply read on: the checkpoints below work either way.

Intro · 2 min

Answering ~14 Billion Questions a Day — In Milliseconds

Imagine this challenge: Someone types three words into a search box.

Within 200 milliseconds, you must search through 400 billion documents — roughly 3,000 times the number of books ever published — find the most relevant results, rank them by quality and relevance, check for spam, personalize the results, and display them in a beautiful interface.

Now do that ~160,000 times per second on average, every second, 24/7.

This is Google Search — the most sophisticated information retrieval system ever built.

Search serving · the query scatters to every index shard, then results gather, merge and rank inside 200 ms
LIVESearch serving · the query scatters to every index shard, then results gather, merge and rank inside 200 ms
The Google Search scale (2025)
Query volume
  • Daily searches: ~14 Billion (older estimates: 8.5 Billion)
  • Searches per second: ~160,000 average
  • Annual searches: 5+ Trillion (Google, 2025)
  • Market share: ~90% globally (StatCounter, 2026)
Index size
  • Indexed documents: ~400 Billion (2020 figure, per Google's testimony in US v. Google)
  • Known URLs: 130+ Trillion (Google, 2016)
  • Index storage: 100+ Petabytes (Google: "well over 100,000,000 gigabytes")
  • New URLs discovered: a constant stream (Google publishes no daily figure)
Infrastructure
  • Data centers: 30+ locations worldwide (Google's data-centre locations page, 2026)
  • Servers (estimated): Millions (Google doesn't disclose; a 2016 third-party estimate put it at ~2.5M)
  • Power consumption: ~43.6 TWh/year (all of Google, 2025, mostly data centers)
  • Response time: typically a few hundred milliseconds (Google publishes no latency SLO)
Business impact
  • Search & other revenue (2025): $224.5 Billion
  • Revenue per search: ~$0.045 (rough: $224.5B ÷ ~5T searches)
  • Users worldwide: Billions

This is the system we'll design today. Google publishes its scale and some history (the 1998 paper, Jeff Dean's 2009 talk), not its current serving internals: the stage budgets, signal weights, shard counts and tiers below are a likely design for an engine like Google's, not Google's actual system.

ExplainerThe whole quest: Google Search

Google answers ~14 billion searches a day against ~400 billion documents; this design targets 50 to 500 billion pages, 100,000 QPS and under 500 ms, with 25 trillion postings (~180 TB) that must be sharded. Async pipelines crawl and index, while serving scatters each query to every shard, gathers ~10,000 candidates and ranks them. The deep dives cover the inverted index and document sharding, PageRank, tail latency at 1,000 shards and per-stage budgets, then replicas and caching at 10x, a fresh tier for breaking news, hot queries, spam and crawl traps.

The Interview Begins#

You walk into the interview room. The interviewer smiles and gestures to the whiteboard.

Interviewer

"Thanks for coming in. Today we're going to work through a system design problem together. I want to see how you think about large-scale distributed systems. Feel free to ask questions — this should be collaborative."

They write on the whiteboard:

Design a Web Search Engine

Build a search engine that can:

  • Index billions of web pages
  • Return relevant results in <500ms
  • Handle 10,000+ queries per second
  • Keep the index fresh (new content discoverable within hours)
  • Rank results by relevance and quality
Consider
  • How do you discover new pages?
  • How do you store and query the index efficiently?
  • How do you rank billions of documents?
  • How do you handle 100K QPS with sub-second latency?
Interviewer

"Take a few minutes to think about this, then walk me through your approach. We have about 45 minutes."

Phase 1 · Stage 1 of 7 · 5 min budget · 1 min read

Phase 1: Requirements Clarification (5 minutes)#

Before diving in, you take a breath and start asking questions.

Your Questions#

Transcript
You

"Before I start designing, I'd like to clarify a few requirements. First, what's the scale of the web we need to index? Are we talking millions or billions of pages?"

Interviewer

"Let's say we need to index around 50 billion pages initially, with the capability to scale to hundreds of billions."

You

"And what's our query volume? Peak QPS?"

Interviewer

"Target 10,000 QPS initially, scaling to 100,000 QPS."

You

"What about freshness requirements? How quickly should new content appear in search results?"

Interviewer

"Critical news should appear within minutes. Regular content within hours. Most content within a few days is acceptable."

You

"For ranking, should we implement something like PageRank, or simpler relevance scoring?"

Interviewer

"Start with basic relevance like TF-IDF, then layer in link-based authority scoring like PageRank."

You

"Perfect. Let me summarize the requirements."

Functional Requirements#

Functional requirements
1. Web crawling
  • Discover and download web pages continuously
  • Respect robots.txt and crawl rate limits
  • Handle various content types (HTML, PDF, etc.)
  • Track page freshness and update frequency
2. Indexing
  • Parse and extract text content from pages
  • Build searchable index from page content
  • Store page metadata (title, URL, timestamp)
  • Support incremental index updates
3. Query processing
  • Parse and understand user queries
  • Retrieve matching documents efficiently
  • Rank results by relevance and quality
  • Support features: autocomplete, spell check, filters
4. Ranking
  • Text relevance scoring (TF-IDF, BM25)
  • Link-based authority (PageRank)
  • Freshness signals
  • Spam detection and filtering

Non-Functional Requirements#

Non-functional requirements
1. Scale
  • 50+ billion indexed pages
  • ~15+ petabytes of page, index and log data
  • 100,000 QPS at peak
2. Latency
  • Query response: <500ms p99
  • Index freshness: <24 hours for most content
  • Breaking news: <15 minutes
3. Availability
  • 99.99% uptime
  • Graceful degradation under load
  • Multi-region deployment
4. Consistency
  • Eventually consistent index
  • Stale reads acceptable (within hours)
  • Strong consistency for user data only

Phase 2 · Stage 2 of 7 · 5 min budget · 2 min read

Phase 2: Back of the Envelope Estimation (5 minutes)#

You

"Let me work through the numbers to understand the scale we're dealing with."

Your turn · checkpoint 1not answered

Before the numbers: will the index fit on one machine?

You index 50 billion pages with about 500 distinct terms each, and a posting (document ID, position, metadata) takes about 12 bytes. How big is the inverted index? What about after 5× compression and 3 replicas? What does that imply for serving?

Think it through, or jot it down. Then compare.

One strong answer

  • Postings: 50B × 500 = 25 trillion.
  • Raw size: 25T × 12 B = 300 TB.
  • Compressed: ÷ 5 = ~60 TB, and ~180 TB with three replicas.

Nowhere near one machine. The index has to be split into thousands of shards served in parallel, with the hot parts in memory, which is why sharding is the first deep dive.

Index Size Estimation#

WorksheetIndex size estimation
  • Document storage
    • Pages to index: 50 billion
    • Average page size (compressed): 50 KB
    • Raw page storage: 50B × 50KB = 2.5 PB
  • Inverted index
    • Unique terms: ~10 billion (Zipf: most appear in only a handful of docs)
    • Total postings: 50B docs × ~500 distinct terms/doc = 25 trillion (only ~2,500 per term on average)
    • Posting entry size: 12 bytes (docID + position + metadata)
    • Inverted index size: 25T × 12B = 300 TB
    • With compression (5x): ~60 TB
    • With replication (3x): ~180 TB total
  • Forward index (document → terms)
    • Per-document metadata: 1 KB
    • Forward index: 50B × 1KB = 50 TB
    • With replication: 150 TB

Query Traffic Estimation#

WorksheetQuery traffic estimation
  • Query volume
    • Peak QPS: 100,000
    • Queries per day: ~5 billion
    • Average query length: ~3-4 words
  • Query processing
    • Documents to score per query: ~10,000 candidates
    • Scoring time per doc: ~10 microseconds
    • Total scoring time: ~100ms
    • Results to return: 10-20 per page
  • Bandwidth
    • Query request size: ~500 bytes
    • Response size: ~50 KB
    • Peak bandwidth: 100K × 50KB = 5 GB/s

Infrastructure Estimation#

Infrastructure summary
  • Storage
    • Index storage — ~0.2 PB of replicated postings (Google's real index, ~400B docs incl. stored page data, is 100+ PB)
    • Document storage — ~10 PB (compressed)
    • Metadata/logs — ~5 PB
  • Compute
    • Index servers — ~10,000 machines
    • Crawlers — ~1,000 machines
    • Query servers — ~5,000 machines
    • Ranking/ML — ~2,000 machines
  • Network
    • Internal bandwidth — 100+ Tbps
    • External bandwidth — 10+ Tbps
  • Data centers
    • Minimum — 5-7 regions globally
Quick check3 questions · +5 XP each

Requirements and estimation

  1. Q1/3 Why is an eventually consistent index acceptable here, when user data needs strong consistency?

    Answer & explanation

    Answer: B

    The requirements accept stale reads within hours for the index, while freshness for breaking news is handled separately. Forcing every shard into strict agreement would slow queries for little benefit. User data is where correctness actually matters.

  2. Q2/3 Each query scores ~10,000 candidates at ~10 microseconds each. If query expansion doubled the candidates to 20,000 at the same cost, roughly how long would scoring take?

    Answer & explanation

    Answer: D

    20,000 x 10 microseconds is 200ms, double the ~100ms baseline. Scoring cost grows linearly with candidates, which is why retrieval caps how many documents reach the scoring stage.

  3. Q3/3 Which item dominates the storage estimate?

    Answer & explanation

    Answer: B

    Total postings are bounded by documents x distinct terms per document (~25 trillion), so the compressed index is only ~60 TB, far below the ~2.5 PB of stored pages. Multiplying 10 billion terms by a big "average postings per term" is the classic trap: most terms are rare.

Phase 3 · Stage 3 of 7 · 10 min budget · 2 min read

Phase 3: High-Level Design (10 minutes)#

You

"Now let me sketch out the high-level architecture. A search engine has three major components: crawling, indexing, and serving."

Your turn · checkpoint 2not answered

Sketch it first: three pipelines, one deadline

Before reading on, name the three big pipelines in a search engine. Which of them has to finish within the user's few-hundred-millisecond budget, and how are the others connected to it?

Think it through, or jot it down. Then compare.

One strong answer

  • Crawling: a URL frontier ordered by importance and freshness, polite fetchers, parsers and link extraction feeding new URLs back.
  • Indexing: cleaning and tokenizing pages, building the inverted index, and computing link authority such as PageRank.
  • Serving: parsing the query, retrieving candidates from the index, ranking them and returning results.

Only serving sits on the user's latency path. Crawling and indexing run asynchronously and may lag by minutes to hours; they hand finished index segments to the serving tier, so a slow crawl makes results staler but never makes a search slower.

System Architecture#

DiagramSearch engine architecture
%% title: Search engine architecture
flowchart TD
  subgraph CP["Crawling pipeline"]
    UF@{ icon: "sd:queue", label: "URL Frontier" } --> CW@{ icon: "sd:robot", label: "Crawler Workers" }
    CW --> CPR["Content Parser"]:::service
    CPR --> PS@{ icon: "sd:database", label: "Page Store" }
  end
  subgraph IP["Indexing pipeline"]
    DP["Document Parser"]:::service --> TA["Tokenizer/Analyzer"]:::service
    TA --> IB["Index Builder"]:::core
    IB --> II@{ icon: "sd:index", label: "Inverted Index" }
    DP --> PR["PageRank Compute"]:::core
  end
  subgraph SL["Serving layer"]
    QP["Query Parser"]:::edge ==> RE["Retrieval Engine"]:::hot
    RE ==> RK["Ranking Engine"]:::core
    RK ==> RS["Results Server"]:::edge
  end
  CP --> IP
  IP --> SL
  class UF queue
  class CW service
  class PS,II store

Component Breakdown#

1. Crawling Pipeline#

Purpose: Continuously discover and download web pages from the internet.

Key components:

  • URL Frontier: Priority queue of URLs to crawl, ordered by importance and freshness
  • Crawler Workers: Distributed fetchers that download pages, respecting rate limits
  • Content Parser: Extract text, links, and metadata from HTML
  • Link Extractor: Discover new URLs to feed back to the frontier

2. Indexing Pipeline#

Purpose: Process crawled pages and build searchable data structures.

Key components:

  • Document Parser: Clean HTML, extract text, handle encoding
  • Tokenizer/Analyzer: Split text into tokens, apply stemming, remove stopwords
  • Index Builder: Construct inverted index data structures
  • PageRank Compute: Calculate link-based authority scores

3. Serving Layer#

Purpose: Handle user queries and return ranked results.

Key components:

  • Query Parser: Parse query, expand synonyms, handle operators
  • Retrieval Engine: Find candidate documents from inverted index
  • Ranking Engine: Score and sort candidates by relevance
  • Results Server: Format and return final results
Quick check2 questions · +5 XP each

High-level design

  1. Q1/2 The crawling pipeline falls a few hours behind. What do users notice?

    Answer & explanation

    Answer: C

    The serving layer reads an index that has already been built, so the query path doesn't depend on crawling. A lagging crawler only means newer pages aren't indexed yet. That's the point of separating crawling, indexing and serving.

  2. Q2/2 A freshly crawled page contains links to 50 pages the system has never seen. What happens to them?

    Answer & explanation

    Answer: B

    Link extraction is how crawling discovers the web. New URLs go into the frontier's priority queue, and crawler workers fetch them in priority order within rate limits, not immediately.

Phase 4 · Stage 4 of 7 · 20 min budget · 13 min read

Phase 4: Deep Dives (20 minutes)#

Interviewer

"Great overview. Let's dive deeper into some challenging aspects. How does the inverted index actually work?"

Deep Dive 1: The Inverted Index (Week 1 - Partitioning & Sharding)#

You

"The inverted index is the heart of any search engine. Let me explain how it works and how we scale it."

What is an Inverted Index?#

Inverted index structure
Traditional index (forward)
Document Words
Doc 1 "the quick brown fox jumps"
Doc 2 "the lazy brown dog sleeps"
Doc 3 "quick fox runs fast"
Inverted index (reverse)
Term Postings List (DocID:Positions)
"brown" [(Doc1, [3]), (Doc2, [3])]
"fox" [(Doc1, [4]), (Doc3, [2])]
"quick" [(Doc1, [2]), (Doc3, [1])]
"lazy" [(Doc2, [2])]
To find "quick fox"
  1. Look up "quick" → {Doc1, Doc3}
  2. Look up "fox" → {Doc1, Doc3}
  3. Intersect → {Doc1, Doc3}
  4. Score and rank

Sharding the Index#

You

"With 400 billion documents and 10 billion unique terms, we need to shard the index across thousands of machines."

Your turn · checkpoint 3not answered

Shard by document or by term?

You could give each shard every term for a slice of the documents, or every document for a slice of the terms. Pick one for a web-scale engine and say what each query then has to do.

Think it through, or jot it down. Then compare.

One strong answer

Document-based sharding (what the lesson's Google design uses, with a tiered index):

  • Every query goes to all shards in parallel; each scores its own documents and returns a local top-k, and a merger combines them.
  • Shards are self-contained, load spreads evenly, and losing a shard drops a slice of documents rather than whole words.
  • The cost is that every query touches every shard, which you pay for with replicas and by searching a small high-quality tier first.

Term-based sharding touches fewer shards per query, but common words create enormous, hot shards, and intersecting postings across machines moves a lot of data over the network.

Index sharding strategies

Option 1: Document-based sharding

Each shard contains ALL terms for a SUBSET of documents

  • Shard 1: Docs 0-1M — "apple" → [...], "banana" → [...]
  • Shard 2: Docs 1M-2M — "apple" → [...], "banana" → [...]
  • Shard N: ...

Query: Search ALL shards in parallel, merge results

  • Pro: Each shard is self-contained
  • Con: Every query hits every shard

Google uses: Document-based sharding (per Jeff Dean's WSDM 2009 keynote), here with a tiered index

Option 2: Term-based sharding

Each shard contains ALL documents for a SUBSET of terms

  • Shard A-F: Terms A-F — "apple" → all, "banana" → all
  • Shard G-L: Terms G-L — "guitar" → all, "house" → all

Query: Only hit shards containing query terms

  • Pro: Fewer shards per query
  • Con: Cross-shard coordination for scoring

Implementation#

pythonPosting · 133 lines
# inverted_index/index_shard.py

"""
Inverted Index Shard Implementation
Demonstrates the core data structures used in search engines.
"""

from dataclasses import dataclass, field
from typing import List, Dict, Optional
from collections import defaultdict
import bisect
import math


@dataclass
class Posting:
    """A single posting entry in the inverted index."""
    doc_id: int
    term_frequency: int
    positions: List[int] = field(default_factory=list)


@dataclass
class PostingsList:
    """List of all postings for a single term."""
    term: str
    doc_frequency: int
    postings: List[Posting] = field(default_factory=list)
    
    def intersect(self, other: 'PostingsList') -> 'PostingsList':
        """
        Intersect two posting lists (for AND queries).
        Uses merge algorithm - O(n + m).
        """
        result = PostingsList(
            term=f"{self.term} AND {other.term}",
            doc_frequency=0,
            postings=[]
        )
        
        i, j = 0, 0
        while i < len(self.postings) and j < len(other.postings):
            if self.postings[i].doc_id == other.postings[j].doc_id:
                combined = Posting(
                    doc_id=self.postings[i].doc_id,
                    term_frequency=self.postings[i].term_frequency + 
                                   other.postings[j].term_frequency,
                    positions=sorted(self.postings[i].positions + 
                                    other.postings[j].positions)
                )
                result.postings.append(combined)
                i += 1
                j += 1
            elif self.postings[i].doc_id < other.postings[j].doc_id:
                i += 1
            else:
                j += 1
        
        result.doc_frequency = len(result.postings)
        return result


class InvertedIndexShard:
    """
    A single shard of the inverted index.
    
    In production:
    - Each shard handles ~1-10M documents
    - Index is memory-mapped for fast access
    - Postings are sorted by doc_id for efficient intersection
    """
    
    def __init__(self, shard_id: int, doc_range: tuple):
        self.shard_id = shard_id
        self.doc_range = doc_range
        self.index: Dict[str, PostingsList] = {}
        self.doc_count = 0
        
    def add_document(self, doc_id: int, tokens: List[str]) -> None:
        """Index a single document."""
        term_positions: Dict[str, List[int]] = defaultdict(list)
        for position, token in enumerate(tokens):
            term_positions[token].append(position)
        
        for term, positions in term_positions.items():
            if term not in self.index:
                self.index[term] = PostingsList(term=term, doc_frequency=0)
            
            posting = Posting(
                doc_id=doc_id,
                term_frequency=len(positions),
                positions=positions
            )
            
            # key= needs Python 3.10+; insort into a list is O(n) per insert
            bisect.insort(
                self.index[term].postings, 
                posting,
                key=lambda p: p.doc_id
            )
            self.index[term].doc_frequency += 1
        
        self.doc_count += 1
    
    def search(self, query_terms: List[str]) -> PostingsList:
        """Search for documents containing all query terms."""
        if not query_terms:
            return PostingsList(term="", doc_frequency=0)
        
        postings_lists = []
        for term in query_terms:
            pl = self.index.get(term)
            if pl is None:
                return PostingsList(term=term, doc_frequency=0)
            postings_lists.append(pl)
        
        # Sort by document frequency (shortest first)
        postings_lists.sort(key=lambda pl: pl.doc_frequency)
        
        # Intersect all posting lists
        result = postings_lists[0]
        for pl in postings_lists[1:]:
            result = result.intersect(pl)
            if result.doc_frequency == 0:
                break
        
        return result
    
    def compute_idf(self, doc_frequency: int) -> float:
        """Compute Inverse Document Frequency."""
        if doc_frequency == 0:
            return 0.0
        return math.log(self.doc_count / doc_frequency)

Deep Dive 2: PageRank Algorithm (Week 5 - Consistency & Coordination)#

Transcript
Interviewer

"Tell me about PageRank. How does Google determine page importance?"

You

"PageRank is the algorithm that made Google famous. It's essentially a voting system where links are votes."

The PageRank Concept#

PageRank: the random surfer model

Imagine a random web surfer:

  • Start on a random page
  • Follow a random link on that page
  • Repeat forever

PageRank = probability that the random surfer lands on a page

Pages with more incoming links → higher probability → higher PageRank

The Mathematics#

PageRank formula

PR(A) = (1-d)/N + d × Σ(PR(i)/L(i))

Where:

  • PR(A) — PageRank of page A
  • d — Damping factor (typically 0.85)
  • N — Total number of pages
  • PR(i) — PageRank of pages linking to A
  • L(i) — Number of outgoing links from page i

The damping factor d is the probability that the random surfer keeps following links; with probability 1-d (0.15) they get bored and jump to a random page.

Implementation#

pythonWebPage · 118 lines
# ranking/pagerank.py

"""
PageRank Computation at Scale
Demonstrates how to compute PageRank for billions of pages.
"""

from dataclasses import dataclass
from typing import Dict, List


@dataclass
class WebPage:
    """Represents a web page in the link graph."""
    url: str
    outgoing_links: List[str]
    incoming_links: List[str] = None
    pagerank: float = 1.0


class PageRankComputer:
    """
    Computes PageRank scores for a web graph.
    
    At web scale (a likely design, not Google's published pipeline):
    - Graph has 100+ billion nodes
    - Computation takes days on thousands of machines
    - Uses MapReduce-style distributed iteration
    """
    
    def __init__(self, damping_factor: float = 0.85, max_iterations: int = 100,
                 convergence_threshold: float = 1e-6):
        self.damping_factor = damping_factor
        self.max_iterations = max_iterations
        self.convergence_threshold = convergence_threshold
        self.pages: Dict[str, WebPage] = {}
        self.num_pages = 0
        
    def add_page(self, url: str, outgoing_links: List[str]) -> None:
        """Add a page to the graph."""
        self.pages[url] = WebPage(url=url, outgoing_links=outgoing_links)
        
        for link in outgoing_links:
            if link not in self.pages:
                self.pages[link] = WebPage(url=link, outgoing_links=[])
        
        self.num_pages = len(self.pages)
    
    def build_reverse_links(self) -> None:
        """Build incoming link lists for each page."""
        for page in self.pages.values():
            page.incoming_links = []
        
        for url, page in self.pages.items():
            for link in page.outgoing_links:
                if link in self.pages:
                    self.pages[link].incoming_links.append(url)
    
    def compute(self) -> Dict[str, float]:
        """
        Compute PageRank using power iteration.
        
        Algorithm:
        1. Initialize all pages with equal rank (1/N)
        2. For each iteration:
           - Sum contributions from incoming links
           - Apply damping factor
        3. Repeat until convergence
        """
        if not self.pages:
            return {}
        
        self.build_reverse_links()
        
        # Initialize PageRank values
        initial_rank = 1.0 / self.num_pages
        for page in self.pages.values():
            page.pagerank = initial_rank
        
        # Iterative computation
        for iteration in range(self.max_iterations):
            new_ranks: Dict[str, float] = {}
            
            # Handle dangling pages
            dangling_sum = sum(
                page.pagerank for page in self.pages.values()
                if len(page.outgoing_links) == 0
            )
            dangling_contribution = dangling_sum / self.num_pages
            
            # Compute new rank for each page
            for url, page in self.pages.items():
                new_rank = (1 - self.damping_factor) / self.num_pages
                new_rank += self.damping_factor * dangling_contribution
                
                for source_url in page.incoming_links:
                    source_page = self.pages[source_url]
                    num_outlinks = len(source_page.outgoing_links)
                    if num_outlinks > 0:
                        contribution = source_page.pagerank / num_outlinks
                        new_rank += self.damping_factor * contribution
                
                new_ranks[url] = new_rank
            
            # Check for convergence
            max_diff = max(
                abs(new_ranks[url] - self.pages[url].pagerank)
                for url in self.pages
            )
            
            for url, rank in new_ranks.items():
                self.pages[url].pagerank = rank
            
            if max_diff < self.convergence_threshold:
                print(f"Converged after {iteration + 1} iterations")
                break
        
        return {url: page.pagerank for url, page in self.pages.items()}
Quick check3 questions · +5 XP each

Inverted index and PageRank

  1. Q1/3 The index uses document-based sharding. Which shards does the query "quick fox" go to?

    Answer & explanation

    Answer: D

    Each document shard holds all terms for a subset of documents, so any shard could have matches and every query fans out to all of them. Only term-based sharding sends a query to just the shards that own its terms, and that design pays for it with cross-shard scoring coordination.

  2. Q2/3 Before intersecting, search sorts posting lists by document frequency, shortest first. Why?

    Answer & explanation

    Answer: B

    Starting with the rarest term keeps every intersection small, and the loop exits as soon as the result is empty. Postings still have to be sorted by doc_id, because that's what makes the O(n + m) merge work.

  3. Q3/3 Pages A and B each have one incoming link. Both source pages have equal PageRank, but A's source has 100 outgoing links and B's source has 2. Who gets more PageRank from that link?

    Answer & explanation

    Answer: C

    A page's PageRank is divided among its outgoing links. B's source gives half its rank to each of 2 links, while A's source spreads its rank over 100. So links aren't equal votes: a link from a selective page counts for more.

Deep Dive 3: Query Processing Pipeline (Week 2 - Timeouts & Latency)#

Transcript
Interviewer

"Walk me through what happens when a user types a query."

You

"Query processing is where all the latency pressure is. We have 500ms to do everything."

Your turn · checkpoint 4not answered

The slowest shard sets the pace

A query fans out to 1,000 shards and waits for all of them. Each shard answers quickly 99% of the time but is occasionally slow. How often does a query hit at least one slow shard, and what would you do about it?

Think it through, or jot it down. Then compare.

One strong answer

  • The maths: the chance that all 1,000 are fast is 0.99^1000 ≈ 0.004%, so almost every query waits on at least one slow shard. At this fan-out, the tail of one shard becomes the median of the whole query.
  • Deadlines and partial results: give each shard a budget and return what has arrived; a slightly incomplete result list beats a slow one.
  • Hedged requests: if a shard hasn't answered within a few milliseconds, ask one of its replicas too and take whichever answers first.
  • Do less work per query: cache results for popular queries, and search the small top tier first, going deeper only when needed.

Query Processing Flow#

DiagramQuery processing timeline (target < 500ms)
%% title: Query processing timeline (target < 500ms)
flowchart TD
  U(["User types: 'best pizza near me'"]):::client
  subgraph S1["Stage 1 · Query understanding (20ms budget)"]
    direction TB
    SP["Spell Check<br/>'piza' → 'pizza'"]:::service
    TK["Tokenize<br/>Split words"]:::service
    SY["Synonyms<br/>best → top"]:::service
    IN["Intent<br/>Local search"]:::service
  end
  RT["STAGE 2 · RETRIEVAL (100ms budget)<br/>Query sent to all index shards in parallel<br/>Intersect posting lists, best candidates per shard merged<br/>Total candidates kept: ~10,000 documents"]:::hot
  SC["STAGE 3 · SCORING (200ms budget)<br/>Text Relevance (BM25): Weight 0.3<br/>PageRank: Weight 0.2 · Freshness: Weight 0.1<br/>Location Match: Weight 0.2 · Click History: Weight 0.2<br/>Sort by final score, take top 100"]:::core
  SN["STAGE 4 · SNIPPET GENERATION (50ms budget)<br/>For top 10 results: fetch document content (or cached snippet)<br/>Find best passage containing query terms<br/>Highlight matching terms"]:::service
  RS(["STAGE 5 · RESPONSE (30ms budget)<br/>Total time: ~400ms (target < 500ms)"]):::ok
  U --> S1
  S1 ==> RT
  RT ==> SC
  SC ==> SN
  SN ==> RS
ExplainerScatter-gather: the slowest shard sets the pace

A query fans out to every document shard in parallel and each returns its local top-k. Waiting for all lets one slow shard set the latency: with 1,000 shards only about 0.004% of queries avoid a slow one. With a deadline and a hedged request to the slow shard's replica, the first answer wins and the root merges about 10,000 candidates inside the 100 ms retrieval budget.

BM25 Scoring Implementation#

pythonBM25Scorer · 73 lines
# query/scorer.py

"""
BM25 Ranking Algorithm
An improvement over TF-IDF that handles term frequency saturation
and document length normalization.
"""

import math
from typing import Dict, List, Optional


class BM25Scorer:
    """
    BM25 scoring for search relevance.
    
    BM25 is the industry standard for text relevance scoring.
    It improves on TF-IDF by:
    - Saturating term frequency (diminishing returns)
    - Normalizing for document length
    """
    
    K1 = 1.2  # Term frequency saturation parameter
    B = 0.75  # Length normalization parameter
    
    def __init__(self, avg_doc_length: float, total_docs: int):
        self.avg_doc_length = avg_doc_length
        self.total_docs = total_docs
    
    def score(self, doc: Dict, query_terms: List[str]) -> float:
        """
        Compute BM25 score for a document.
        
        Args:
            doc: Document with term_freqs, doc_length, doc_freqs
            query_terms: List of query terms
            
        Returns:
            BM25 score (higher = more relevant)
        """
        score = 0.0
        doc_length = doc.get("doc_length", self.avg_doc_length)
        term_freqs = doc.get("term_freqs", {})
        
        for term in query_terms:
            tf = term_freqs.get(term, 0)
            if tf == 0:
                continue
            
            # IDF component
            df = doc.get("doc_freqs", {}).get(term, 1)
            idf = math.log((self.total_docs - df + 0.5) / (df + 0.5) + 1)
            
            # TF component with saturation
            numerator = tf * (self.K1 + 1)
            denominator = tf + self.K1 * (
                1 - self.B + self.B * (doc_length / self.avg_doc_length)
            )
            
            score += idf * (numerator / denominator)
        
        return score
    
    def compute_freshness(self, last_updated) -> float:
        """Compute freshness score with exponential decay."""
        from datetime import datetime
        
        if last_updated is None:
            return 0.5
        
        age_days = (datetime.utcnow() - last_updated).days
        half_life = 30  # 30-day half-life
        return math.exp(-0.693 * age_days / half_life)

Deep Dive 4: Web Crawling at Scale (Week 3 - Async Processing)#

Transcript
Interviewer

"How do you crawl billions of pages efficiently?"

You

"Crawling is fundamentally an async, distributed problem."

Crawling Architecture#

DiagramWeb crawling pipeline
%% title: Web crawling pipeline
flowchart TD
  UF@{ icon: "sd:queue", label: "URL FRONTIER<br/>Priority Queue of URLs to crawl, ordered by:<br/>Importance (PageRank of linking pages)<br/>Freshness (time since last crawl)<br/>Domain priority (news sites crawled more often)" }
  CW@{ icon: "sd:robot", label: "CRAWLER WORKERS<br/>1000s of distributed workers fetching pages in parallel" }
  CPR["CONTENT PROCESSOR<br/>Parse HTML → Extract Links & Text →<br/>Detect Language → Store Content"]:::service
  UF ==> CW
  CW ==> CPR
  CPR -.->|"New URLs fed back"| UF
  class UF queue
  class CW core

POLITENESS CONSTRAINTS (crawler workers):

  • Max 1 request per second per domain
  • Respect robots.txt
  • Respect Crawl-Delay directive

Crawler Implementation#

pythonCrawlURL · 160 lines
# crawler/web_crawler.py

"""
Distributed Web Crawler
Demonstrates key crawling concepts:
- Politeness (rate limiting per domain)
- Priority-based URL frontier
- Parallel fetching with async IO
"""

from dataclasses import dataclass, field
from typing import Dict, List, Optional, Set
from datetime import datetime
from urllib.parse import urlparse, urljoin
import asyncio
import heapq


@dataclass
class CrawlURL:
    """A URL in the crawl frontier."""
    url: str
    priority: float
    last_crawl: Optional[datetime] = None
    depth: int = 0
    
    def __lt__(self, other):
        return self.priority > other.priority


class URLFrontier:
    """
    Priority queue of URLs to crawl.
    
    Key features:
    - Priority-based ordering
    - Deduplication
    - Per-domain queuing for politeness
    """
    
    def __init__(self, max_size: int = 10_000_000):
        self.max_size = max_size
        self.queue: List[CrawlURL] = []
        self.seen_urls: Set[str] = set()
        self.domain_last_access: Dict[str, datetime] = {}
        
    def add(self, url: str, priority: float = 0.5, depth: int = 0) -> bool:
        """Add URL to frontier if not seen."""
        url = url.lower().rstrip("/").split("#")[0]
        
        if url in self.seen_urls:
            return False
        
        if len(self.seen_urls) >= self.max_size:
            return False
        
        crawl_url = CrawlURL(url=url, priority=priority, depth=depth)
        heapq.heappush(self.queue, crawl_url)
        self.seen_urls.add(url)
        return True
    
    def get_next(self) -> Optional[CrawlURL]:
        """Get next URL respecting politeness constraints."""
        if not self.queue:
            return None
        
        now = datetime.utcnow()
        deferred: List[CrawlURL] = []  # blocked by politeness; re-queue after the scan
        result: Optional[CrawlURL] = None
        
        while self.queue:
            crawl_url = heapq.heappop(self.queue)
            domain = urlparse(crawl_url.url).netloc
            
            last_access = self.domain_last_access.get(domain)
            if last_access and (now - last_access).total_seconds() < 1.0:
                deferred.append(crawl_url)
                continue
            
            self.domain_last_access[domain] = now
            result = crawl_url
            break
        
        for item in deferred:
            heapq.heappush(self.queue, item)
        return result


class WebCrawler:
    """
    Distributed web crawler with async IO.
    """
    
    def __init__(self, num_workers: int = 100, max_pages: int = 1_000_000):
        self.num_workers = num_workers
        self.max_pages = max_pages
        self.frontier = URLFrontier()
        self.pages_crawled = 0
    
    def add_seeds(self, seed_urls: List[str]) -> None:
        """Add seed URLs to start crawling."""
        for url in seed_urls:
            self.frontier.add(url, priority=1.0, depth=0)
    
    async def crawl(self) -> None:
        """Start crawling with multiple workers."""
        workers = [
            asyncio.create_task(self._worker(i))
            for i in range(self.num_workers)
        ]
        await asyncio.gather(*workers)
    
    async def _worker(self, worker_id: int) -> None:
        """Single crawler worker."""
        import aiohttp
        
        async with aiohttp.ClientSession(
            headers={"User-Agent": "GoogleBot/2.1"}
        ) as session:
            while self.pages_crawled < self.max_pages:
                crawl_url = self.frontier.get_next()
                if crawl_url is None:
                    await asyncio.sleep(0.1)
                    continue
                
                try:
                    async with session.get(
                        crawl_url.url,
                        timeout=aiohttp.ClientTimeout(total=30)
                    ) as response:
                        if response.status == 200:
                            content = await response.text()
                            links = self._extract_links(crawl_url.url, content)
                            
                            for link in links:
                                self.frontier.add(
                                    link, priority=0.5,
                                    depth=crawl_url.depth + 1
                                )
                            
                            self.pages_crawled += 1
                except Exception:
                    pass
    
    def _extract_links(self, base_url: str, html: str) -> List[str]:
        """Extract links from HTML content."""
        import re
        links = []
        href_pattern = re.compile(r'href=["\']([^"\']+)["\']')
        
        for match in href_pattern.finditer(html):
            href = match.group(1)
            if href.startswith(("javascript:", "mailto:", "#")):
                continue
            
            absolute_url = urljoin(base_url, href)
            if absolute_url.startswith(("http://", "https://")):
                links.append(absolute_url)
        
        return links
Quick check3 questions · +5 XP each

Query processing and crawling

  1. Q1/3 Two documents of similar length mention "pizza" 50 times and 5 times. How does BM25 treat that gap compared with raw term frequency?

    Answer & explanation

    Answer: C

    The k1 term gives diminishing returns, so repeating a word keeps adding less and less. That's one of BM25's two improvements over TF-IDF; the other is length normalization. It doesn't flag spam by itself.

  2. Q2/3 The top 1,000 URLs in the frontier all belong to one news site. With a limit of 1 request per second per domain, what does the frontier do?

    Answer & explanation

    Answer: A

    get_next skips a URL whose domain was hit less than a second ago and moves on to other work. High priority decides the order, but it never overrides politeness: hammering one site would overload it and get the crawler blocked.

  3. Q3/3 Why split the 500ms target into per-stage budgets (understanding 20ms, retrieval 100ms, scoring 200ms, and so on)?

    Answer & explanation

    Answer: D

    With a fixed budget per stage, each stage can cut off and move on with what it has, which keeps the end-to-end p99 under control. Without budgets, a single slow step pushes the whole response past its target.

Phase 5 · Stage 5 of 7 · 5 min budget · 1 min read

Phase 5: Scaling and Edge Cases (5 minutes)#

Interviewer

"How would you scale this to handle 10x traffic?"

Scaling Strategy#

Scaling from 10K to 100K QPS — scaling strategy

Query path

Current: 10K QPS → Target: 100K QPS

Strategy:

  1. Add more query servers (10x)
  2. Increase index shard replicas (3x → 10x)
  3. Add query result cache (hit rate ~30%)
  4. Deploy in more regions (latency optimization)

Index path

Current: 50B documents → Target: 500B documents

Strategy:

  1. Add more index shards (10x)
  2. Tiered storage (hot/warm/cold)
  3. Better compression (reduce storage cost)

Edge Cases#

Edge Case 1: Query Spikes#

  • Scenario: Breaking news causes 10x traffic spike
  • Problem: Index shards for hot terms get overwhelmed
  • Solution:
    • Query result caching with short TTL
    • Replicate hot index shards dynamically
    • Graceful degradation (return slightly stale results)

Edge Case 2: Index Freshness#

  • Scenario: Celebrity dies, but search shows them as "alive"
  • Problem: Cached PageRank and index data is stale
  • Solution:
    • Breaking news detection pipeline
    • Fast-path indexing for high-priority content
    • Real-time signals from trusted sources

Edge Case 3: Spam Attacks#

  • Scenario: SEO farms create millions of spam pages
  • Problem: Index quality degrades
  • Solution:
    • Link graph analysis (detect link farms)
    • Content fingerprinting (detect duplicates)
    • ML spam classifiers

Edge Case 4: Crawl Traps#

  • Scenario: Website generates infinite URLs
  • Problem: Crawler gets stuck, wastes resources
  • Solution:
    • Detect URL patterns generating infinite content
    • Limit crawl depth per domain
    • Track unique content ratio
Quick check3 questions · +5 XP each

Scaling and edge cases

  1. Q1/3 To go from 10K to 100K QPS without growing the corpus, why add index shard replicas rather than more shards?

    Answer & explanation

    Answer: C

    Query throughput is limited by how many copies of each shard can answer. More shards is the lever for growing from 50B to 500B documents, not for serving more queries.

  2. Q2/3 A site generates an endless stream of unique URLs, like a calendar with a "next month" link that never ends. What protects the crawler?

    Answer & explanation

    Answer: B

    A crawl trap produces new URLs with little new content. Depth limits and a unique-content ratio catch it. Crawling faster would only waste more resources on the trap.

  3. Q3/3 Breaking news causes a 10x spike in identical queries. Why use a result cache with a short TTL?

    Answer & explanation

    Answer: A

    Thousands of users sending the same query can share one computed result, which takes load off the hot shards. A short TTL accepts slightly stale results in exchange for that protection, without freezing results for a story that's still developing.

Phase 6 · Stage 6 of 7 · 1 min

Phase 6: Monitoring and Operations#

Monitoring Dashboard#

Search engine monitoring
Query performance
Metric Value Gauge
QPS 87,432 /sec 87%
Latency p50 145 ms 29%
Latency p99 412 ms 82%
Error rate 0.02% OK
Index health
Metric Value Gauge
Indexed docs 48.2B 96%
Index freshness 2.3 hours avg OK
Shard replicas healthy 99.97% OK
Quality signals
Metric Value Gauge
Click-through rate 34.2% ↑2%
Spam detection rate 99.3% OK

Alerting Strategy#

CRITICAL (Page immediately)

  • Query latency p99 > 1s for > 2 min
  • Error rate > 1%
  • Index shard unavailable

WARNING (Slack notification)

  • Query latency p99 > 500ms for > 5 min
  • Index freshness > 12 hours
  • Click-through rate drops > 10%
Quick check3 questions · +5 XP each

Watching search quality

  1. Q1/3 Click-through rate drops by more than 10% while latency and error rate look normal. What is the most likely cause?

    Answer & explanation

    Answer: B

    Users clicking less on fast, error-free pages points at relevance. Quality metrics catch regressions that infrastructure metrics never see, which is why CTR has its own alert.

  2. Q2/3 Index freshness climbs from 2 hours to 14 hours while query latency is fine. Where is the problem?

    Answer & explanation

    Answer: B

    Serving only reads finished index segments, so it stays fast while the pipeline behind it falls behind. Freshness is the metric that exposes a stalled crawl or indexer.

  3. Q3/3 With document-based sharding, why is "index shard unavailable" a critical alert even if every query still returns results?

    Answer & explanation

    Answer: B

    A missing document shard doesn't break queries, it quietly removes a share of the web from all of them. Silent incompleteness is exactly the kind of failure that needs a loud alert.

Final challenge · Stage 7 of 7 · 2 min

Final Challenge: Breaking News in Fifteen Minutes#

The interviewer checks the time. "Let's end on freshness."

Interviewer

"A major earthquake hits a big city. Within minutes, millions of people search for it, and the pages that matter were published five minutes ago. Your requirement says breaking news within 15 minutes. How does that work in your design?"

The breaking-news brief
  • Relevant pages are minutes old, so they're in no index shard yet
  • One topic suddenly takes a large share of all queries
  • Rumour and spam pages appear just as fast as real reporting
  • Everything else must keep working as normal
Your turn · checkpoint 5not answered

Fresh results without rebuilding the world

Rebuilding or merging hundreds of terabytes of index takes hours. How do minutes-old pages reach results, and how do you keep one exploding query from overloading every shard?

Think it through, or jot it down. Then compare.

One strong answer

  • A fast path into the index: prioritised crawling of feeds, sitemaps and trusted news sources, with new pages indexed incrementally into a small, in-memory fresh tier that takes writes in seconds and is merged into the main index later.
  • Query both tiers: the query hits the main index and the fresh tier and merges the results.
  • Rank for the moment: detect the query spike, treat it as news-seeking, and boost freshness and source authority for it.
  • Protect serving: cache results for the hot query and its variants with a short TTL so repeats don't fan out to every shard, and add replicas for the overloaded shards.
  • Guard quality: lean on source trust and spam signals, since breaking news is exactly when bad pages try to rank.
Quick check4 questions · +5 XP each

Final challenge · Quest check

  1. Q1/4 Why add a small fresh tier instead of rebuilding the main index more often?

    Answer & explanation

    Answer: B

    Big immutable segments are efficient to serve but slow to rebuild. A small mutable tier absorbs new pages immediately and is folded into the main index later.

  2. Q2/4 One query suddenly makes up a large share of all traffic. What is the quickest effective protection?

    Answer & explanation

    Answer: A

    Identical queries within a few seconds can share one answer, so most of the spike never fans out to the shards. A short TTL keeps the cached results fresh enough for news.

  3. Q3/4 A query fans out to 1,000 shards, each fast 99% of the time. Roughly how likely is it that all of them are fast?

    Answer & explanation

    Answer: D

    0.99^1000 ≈ 0.00004. That's why fan-out systems rely on deadlines, partial results and hedged requests rather than waiting for the slowest shard.

  4. Q4/4 In PageRank with damping factor d = 0.85, what does 1 − d = 0.15 represent?

    Answer & explanation

    Answer: B

    The random surfer follows a link with probability d and jumps anywhere with probability 1 − d. The jump is what guarantees every page a small base score and makes the computation converge.

Wrap-up · 7 min

Interview Conclusion#

Transcript
Interviewer

"Excellent work. You've demonstrated strong understanding of distributed systems, information retrieval, and the specific challenges of building a search engine. Any questions?"

You

"Thank you! I'd love to hear more about how your team handles the cold start problem for new content — how quickly can fresh content appear in search results?"

Summary: Concepts Applied#

Concepts from 10-week course in Google Search
  • Week 1: Data at scale
    • Partitioning — Index sharded by document ID
    • Replication — Each shard replicated 3-10x
    • Hot Keys — Popular queries cached, hot shards replicated
  • Week 2: Failure-first design
    • Timeout budgets — Query stages have strict time limits
    • Graceful degradation — Return partial results on timeout
    • Circuit breakers — Stop sending to unhealthy shards
  • Week 3: Messaging & async
    • Async processing — Crawler uses async IO
    • Rate limiting — Per-domain crawl limits
    • Backpressure — URL frontier limits prevent exhaustion
  • Week 5: Consistency & coordination
    • Eventual consistency — Index updates over minutes/hours
    • Distributed computation — PageRank via MapReduce
  • Week 10: Production readiness
    • SLOs — Query latency p99 < 500ms, 99.99% availability
    • Observability — QPS, latency, error rates, quality signals
Why Google Search is an engineering marvel
Scale
  • ~14 billion queries per day (5T+/year) answered in milliseconds
  • ~400 billion documents indexed and searchable (2020 figure)
  • 130+ trillion URLs known to exist on the web (Google, 2016)
Latency
  • Sub-500ms response time for complex queries (this design's target; Google publishes no latency SLO)
  • Results from thousands of index shards merged in real time (this design)
Quality
  • PageRank revolutionized web search quality
  • 200+ ranking signals for relevance (Google's long-cited figure)
  • 99%+ of search visits spam-free (Google's webspam reports, 2020-2021)

Google's mission: "to organize the world's information and make it universally accessible and useful."

Sources#

Official Documentation:

Statistics:

Architecture:

Further Reading#

Books:

  • Book

    "Introduction to Information Retrieval" by Manning, Raghavan, Schütze

  • Book

    "The Google Story" by David Vise

Papers:

  • Read

    "The PageRank Citation Ranking" - Page, Brin, Motwani & Winograd, 1998

  • Read

    "MapReduce: Simplified Data Processing" - Dean & Ghemawat, 2004

  • Read

    "The Google File System" - Ghemawat et al., 2003

Self-Assessment Checklist#

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

After studying this case study, you should be able to0/9 ready
  • Explain how an inverted index works
  • Describe the PageRank algorithm
  • Design a distributed crawling system
  • Explain how to shard an index across thousands of machines
  • Describe the query processing pipeline with timeout budgets
  • Calculate back-of-envelope estimates for a search engine
  • Explain BM25 scoring
  • Describe strategies for index freshness
  • Design monitoring and alerting for search quality

Appendix: Advanced Topics#

D. HITS Algorithm (Hubs and Authorities)#

  • Authority: Page with good content (many pages link TO it)
  • Hub: Page that links to good content (links TO many authorities)

Mutually reinforcing:

  • Good hubs link to good authorities
  • Good authorities are linked from good hubs

Algorithm:

  1. Initialize all pages with authority=1, hub=1
  2. Authority update: auth(p) = Σ hub(q) for all q linking to p
  3. Hub update: hub(p) = Σ auth(q) for all q that p links to
  4. Normalize scores
  5. Repeat until convergence

Use case:

  • Query-specific ranking (PageRank is query-independent)
  • Find topic experts and resource pages

E. Real-Time Search Signals#

Query Click Data#

Click data for ranking

Google has confirmed that it uses click and interaction data in ranking (its Navboost system, in testimony in US v. Google, 2023). It hasn't published the signals; a likely design would extract:

Click-through rate (CTR)
  • Higher CTR = More relevant for that query
  • Adjusted for position (top results naturally get more clicks)
Dwell time
  • Time spent on page after clicking
  • Long dwell = User found what they wanted
  • Short dwell + back button = Poor result ("pogo-sticking")
Query reformulation
  • User searches again with different terms?
  • Original results were poor
Last click
  • The final result user clicked (didn't come back)
  • Strong signal of satisfaction

Privacy considerations (in this design):

  • Data is aggregated (no individual tracking)
  • Minimum threshold of queries before using signal
  • Used for ranking, not targeting

Breaking News Detection#

Goal: Index critical news within minutes

DiagramBreaking news architecture
%% title: Breaking news architecture
flowchart TD
  subgraph SS["Signal sources"]
    direction TB
    TT@{ icon: "sd:chart", label: "Twitter Trends" }
    GT@{ icon: "sd:chart", label: "Google Trends" }
    NF@{ icon: "sd:feed", label: "News Feeds" }
    GA@{ icon: "sd:bell", label: "Google Alerts" }
  end
  BD["BREAKING NEWS DETECTOR<br/>Spike detection in query volume<br/>New entity mentions (people, places, events)<br/>Sentiment shift detection<br/>Cross-source corroboration"]:::core
  PQ@{ icon: "sd:queue", label: "PRIORITY CRAWL QUEUE<br/>Jump queue for news sources<br/>Crawl trusted sources first (AP, Reuters, BBC)<br/>Multiple crawls as story develops" }
  RI@{ icon: "sd:index", label: "REAL-TIME INDEX (TIER 0)<br/>Bypasses normal index build pipeline<br/>Direct injection into serving index<br/>Searchable within 2-5 minutes" }
  TT & GT & NF & GA --> BD
  BD ==> PQ
  PQ ==> RI
  class TT,GT,NF,GA external
  class PQ queue
  class RI store
Location-aware search

User query: "pizza"

Without location: Generic results about pizza

With location (user in NYC):

  1. Detect user location (IP, GPS, profile)
  2. Expand query: "pizza" → "pizza near [lat, lng]"
  3. Boost local business results
  4. Show map pack with nearby options

Location signals:

Explicit
  • Query contains location: "pizza in brooklyn"
  • User profile location setting
  • GPS from mobile device
Implicit
  • IP geolocation (~city level accuracy)
  • WiFi network location
  • Search history patterns
  • Time zone from browser

Search Personalization#

Personalization signals

Google says your location, past Search history and Search settings help tailor results (How Search Works). The signals below are a likely design for an engine's personalization, not Google's published list:

1. Search history
  • Previous queries indicate interests
  • "java" for programmer → programming results
  • "java" for traveler → Indonesia results
2. Location history
  • Frequent locations affect local results
  • Home vs work vs travel
3. Device type
  • Mobile: Emphasize mobile-friendly pages
  • Desktop: May show more detailed results
4. Language preferences
  • Browser language
  • Past language selections
5. Signed-in account data
  • With personal results turned on, Google can show a user's own items, such as Gmail flight confirmations or Calendar events, as separate results
  • Interests from other services (an assumption, not a published Google signal)

Privacy controls:

  • Users can disable personalization
  • Incognito mode = no account-based personalization
  • Data can be deleted from account

G. Search Quality Evaluation#

Human Raters and E-E-A-T#

Search quality evaluation

Google works with about 16,000 external "Search Quality Raters" (contractors, per Google's 2023 overview) who:

  • Evaluate search results for quality
  • Don't directly affect rankings
  • Train ML models and validate algorithm changes
E-E-A-T Framework (Experience, Expertise, Authoritativeness, Trustworthiness)
Pillar Question Example
Experience Does the content creator have first-hand experience? Product review from someone who actually used it
Expertise Does the creator have relevant knowledge/skill? Medical advice from a doctor
Authoritativeness Is the creator/site recognized as a go-to source? Government site for tax information
Trustworthiness Is the content accurate, honest, safe? Secure site, accurate facts, transparent authorship
YMYL (Your Money or Your Life) pages
  • Health, finance, safety, legal topics
  • Held to higher E-E-A-T standards
  • Misinformation can cause real harm

A/B Testing Search Changes#

Search experiment framework

Google evaluates proposed changes with raters and live-traffic experiments before launch (How Search Works). A likely experiment setup:

Experiment setup
  • Control group: Current algorithm
  • Treatment group: New algorithm
  • Random 1-5% of traffic to treatment
Metrics measured
  • Click-through rate (CTR)
  • Time to first click
  • Pogo-stick rate (quick backs)
  • Query abandonment rate
  • Long clicks (dwell time > 30s)
  • Query reformulation rate
Launch criteria
  • Statistically significant improvement
  • No regression on key metrics
  • Human rater evaluation positive
  • No increase in policy violations

Google runs 10,000+ live-traffic experiments per year (16,871 in 2023) — and launched 4,781 changes in 2023.

Neural Ranking Models#

Evolution of ranking
  • Traditional (2000s): TF-IDF + PageRank + Hand-crafted features → Linear combination of ~50 signals
  • Learning to Rank (2010s): Gradient boosted trees (LambdaMART) → Nonlinear combination of ~200 signals
  • Neural Ranking (2020s): BERT, T5, and transformer models → Semantic understanding of queries and documents
BERT in search

Query: "can you get medicine for someone pharmacy"

Before BERT:

  • Matches keywords: medicine, pharmacy
  • Misses intent: picking up prescription for another person

With BERT:

  • Understands "for someone" = on behalf of another person
  • Returns results about pharmacy pickup policies

Key insight: BERT understands prepositions and context

Generative AI Search (AI Overviews)#

AI Overviews (2024-2025)

Google now generates AI summaries for many queries.

Query: "how to remove coffee stains from carpet"

Traditional results:

  1. [Link to WikiHow article]
  2. [Link to cleaning blog]
  3. [Link to product review]

AI Overview:

"To remove coffee stains from carpet:

  1. Blot (don't rub) excess liquid immediately
  2. Mix 1 tablespoon dish soap + 1 tablespoon vinegar + 2 cups water
  3. Apply solution to stain, blot with clean cloth
  4. Rinse with cold water and blot dry

[Sources: WikiHow, Good Housekeeping, The Spruce]"

Challenges:

  • Attribution and copyright
  • Accuracy verification
  • Impact on publisher traffic
  • Hallucination prevention
  • Far more compute per query than classic retrieval

This comprehensive appendix covers advanced topics in search engine design that demonstrate the depth and complexity of building a system like Google Search. These concepts build on the foundational Week 1-10 curriculum and show how theory translates to production systems at massive scale.

Document Statistics:

  • Total concepts covered: 25+
  • Code implementations: 4
  • Architecture diagrams: 4
  • Real-world scale numbers: 50+

this lesson in shorts · 3

play them all →

One idea from this lesson in about a minute each, made for your phone.

End of Side quest 05 · Google Search
Clear the questFinish Google Search.Stages clear as you read them. Marking complete unlocks after ~5 more min of reading.
  • 0/7stages cleared

Follow-up drills

0 of 10 practised

You presented Google Search. Now the interviewer pushes back. Answer each follow-up out loud or in the box, then reveal a strong answer.

Follow-up 1 / 10 · scale & numbers

“How big is the inverted index for 50 billion pages, and what part of storage actually dominates?”

Pushes on · estimating postings without the classic trap

Revealing marks it practised.
design sheet · 7 diagrams · pdfRevise the whole design in ten minutesThe numbers, architecture, sequence, class and state diagrams, decisions and failure modes of this quest on a few printable pages.open the sheet →

Go deeper · reading trail

all reading →

Real write-ups from the teams that built these systems. Each opens on the publisher's site in a new tab.

GooglePaper · ~45 min

The Anatomy of a Large-Scale Hypertextual Web Search Engine

Sergey Brin, Lawrence Page · 1998
What
The paper describing the original Google prototype.
Key idea
Crawling, a forward and inverted index split into barrels, and PageRank computed from the link graph to rank results.
Why here
The crawl, index and rank pipeline of this quest at its origin, including the PageRank deep dive.
taught here: PageRank
GooglePaper · ~40 min

Large-scale Incremental Processing Using Distributed Transactions and Notifications

Daniel Peng, Frank Dabek · 2010
What
The paper on Percolator, the system that let Google update its index continuously.
Key idea
Cross-row transactions and notifications on top of Bigtable let each crawled document update the index incrementally instead of rebuilding it in batch.
Why here
The indexing pipeline of this quest when freshness matters: incremental updates instead of periodic full rebuilds.
taught here: Incremental indexing
GoogleDocs · ~8 min

Automatically generating and ranking results

What
Google's public explanation of how Search ranks results.
Key idea
Ranking weighs many signals (meaning of the query, relevance, quality, usability, context) and the weight of each depends on the query, such as freshness for news.
Why here
The serving layer of this quest: BM25-style relevance is one input among many, and query intent decides how they are mixed.
taught here: Ranking results

New lessons by email

New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.

Discussion · …

Public: everyone reading this lesson sees comments. Be kind and keep it on the material.

Sign in to ask a question or answer one. Reading stays open to everyone.
loading the discussion…