Case Study: How Search Engines Use Loops — Crawling, Indexing, and Ranking

The Scenario

You type "best pizza near me" into a search engine and get results in 0.3 seconds. Behind that fraction of a second, the search engine has already done an astonishing amount of loop-driven work: crawling billions of web pages, indexing their content into a searchable structure, and ranking the results by relevance. Every one of these steps is, at its core, a loop — or millions of loops running simultaneously.

This case study examines the three pillars of search engines through the lens of the loop constructs you learned in Chapter 5. We won't build Google (that's a multi-billion-dollar engineering effort), but we can understand the fundamental patterns.

Phase 1: Crawling (The while Loop That Never Sleeps)

A web crawler (also called a spider) is a program that visits web pages and follows their links to discover new pages. The basic algorithm is a while loop:

while there are URLs to visit:
    take the next URL from the queue
    download the page
    extract all links from the page
    for each link found:
        if we haven't seen this link before:
            add it to the queue

Here's a simplified Python simulation:

# Simplified web crawler simulation
# In reality, this would fetch actual web pages
pages_to_visit = ["homepage.com"]
visited_pages = []
page_index = {}

# Simulated web: each page "contains" links to other pages
simulated_web = {
    "homepage.com": {
        "content": "Welcome to our pizza restaurant",
        "links": ["menu.com", "reviews.com", "about.com"]
    },
    "menu.com": {
        "content": "Margherita pizza pepperoni pizza calzone",
        "links": ["homepage.com", "specials.com"]
    },
    "reviews.com": {
        "content": "Best pizza in town five stars amazing",
        "links": ["homepage.com"]
    },
    "about.com": {
        "content": "Family owned since 1985 authentic Italian",
        "links": ["homepage.com", "menu.com"]
    },
    "specials.com": {
        "content": "Tuesday special two for one pizza deal",
        "links": ["menu.com"]
    }
}

# Crawl loop — a while loop that processes pages until none remain
while len(pages_to_visit) > 0:
    current_url = pages_to_visit.pop(0)  # Take first URL from queue

    if current_url in visited_pages:
        continue  # Skip pages we've already crawled

    # "Download" the page (in reality, this is an HTTP request)
    if current_url in simulated_web:
        page = simulated_web[current_url]
        visited_pages.append(current_url)
        page_index[current_url] = page["content"]

        print(f"Crawled: {current_url}")
        print(f"  Content: {page['content'][:50]}...")
        print(f"  Found {len(page['links'])} links")

        # Add new links to the queue (for loop inside while loop)
        for link in page["links"]:
            if link not in visited_pages:
                pages_to_visit.append(link)

print(f"\nCrawl complete: visited {len(visited_pages)} pages")

Output:

Crawled: homepage.com
  Content: Welcome to our pizza restaurant...
  Found 3 links
Crawled: menu.com
  Content: Margherita pizza pepperoni pizza calzone...
  Found 2 links
Crawled: reviews.com
  Content: Best pizza in town five stars amazing...
  Found 1 links
Crawled: about.com
  Content: Family owned since 1985 authentic Italian...
  Found 2 links
Crawled: specials.com
  Content: Tuesday special two for one pizza deal...
  Found 1 links

Crawl complete: visited 5 pages

What to Notice

  • The outer while loop is condition-controlled: it runs as long as there are pages left to visit. The crawler doesn't know in advance how many pages exist — new pages are discovered as it goes.
  • The inner for loop iterates over the links found on each page, adding undiscovered ones to the queue.
  • continue is used to skip pages that have already been visited. Together with the if link not in visited_pages check, it prevents an infinite loop (imagine two pages that link to each other — homepage.com and menu.com do exactly that).
  • This is a while loop that generates its own work: each iteration can add new items to process. Real web crawlers handle billions of URLs this way.

Phase 2: Indexing (The Accumulator Pattern at Scale)

Once pages are crawled, their content needs to be organized so it can be searched quickly. This is indexing — building a lookup table that maps words to the pages that contain them.

# Build an inverted index: word -> list of pages containing that word
# This is the accumulator pattern applied to build a dictionary

inverted_index = {}

for url, content in page_index.items():
    words = content.lower().split()  # Split content into words
    for word in words:
        if word not in inverted_index:
            inverted_index[word] = []     # Initialize accumulator for this word
        inverted_index[word].append(url)  # Update accumulator

# Display the index
print("Inverted Index:")
print("-" * 50)
for word in sorted(inverted_index.keys()):
    pages = inverted_index[word]
    print(f"  '{word}' -> {pages}")

Output:

Inverted Index:
--------------------------------------------------
  '1985' -> ['about.com']
  'amazing' -> ['reviews.com']
  'authentic' -> ['about.com']
  'best' -> ['reviews.com']
  'calzone' -> ['menu.com']
  'deal' -> ['specials.com']
  'family' -> ['about.com']
  'five' -> ['reviews.com']
  'for' -> ['specials.com']
  'in' -> ['reviews.com']
  'italian' -> ['about.com']
  'margherita' -> ['menu.com']
  'one' -> ['specials.com']
  'our' -> ['homepage.com']
  'owned' -> ['about.com']
  'pepperoni' -> ['menu.com']
  'pizza' -> ['homepage.com', 'menu.com', 'menu.com', 'reviews.com', 'specials.com']
  'restaurant' -> ['homepage.com']
  'since' -> ['about.com']
  'special' -> ['specials.com']
  'stars' -> ['reviews.com']
  'to' -> ['homepage.com']
  'town' -> ['reviews.com']
  'tuesday' -> ['specials.com']
  'two' -> ['specials.com']
  'welcome' -> ['homepage.com']

What to Notice

  • This is the accumulator pattern where we're building a dictionary (you'll learn about dictionaries in Chapter 9). Each word accumulates a list of pages.
  • The word "pizza" appears on four pages: homepage.com, menu.com, reviews.com, and specials.com. Notice that menu.com is listed twice — "pizza" appears twice in its content, and the loop appends the URL once per occurrence. That's not a bug here: the ranking step below uses those repeats to count how often a word appears on a page.
  • Two nested for loops: the outer loop iterates over pages, the inner loop iterates over words in each page.
  • Real search engines process billions of pages and trillions of words. The pattern is identical — just at an incomprehensible scale.

Phase 3: Ranking (Loops for Scoring)

When you search for "pizza," the index tells us which pages contain the word. But which page should appear first? This requires ranking — and ranking is another loop with an accumulator.

A simplified scoring approach: a page's score for a query is determined by how many times the query words appear on that page (this is a gross simplification of real ranking, but it illustrates the pattern). The code is wrapped in a function with def, which you'll learn to write in Chapter 6; for now, focus on the loops inside it:

def search(query, inverted_index, page_index):
    """Search for a query and rank results by word frequency."""
    query_words = query.lower().split()

    # Score each page — accumulator pattern for each page's relevance
    page_scores = {}
    for word in query_words:
        if word in inverted_index:
            for url in inverted_index[word]:
                if url not in page_scores:
                    page_scores[url] = 0       # Initialize
                page_scores[url] += 1          # Accumulate

    # Sort pages by score (highest first) — we'll learn proper sorting in Ch 19
    results = []
    for url, score in page_scores.items():
        results.append((score, url))
    results.sort(reverse=True)

    return results

# Perform a search
query = "best pizza"
print(f"Search results for '{query}':")
print("-" * 40)
results = search(query, inverted_index, page_index)

for rank, (score, url) in enumerate(results, 1):
    content_preview = page_index[url][:60]
    print(f"  {rank}. [{score} match(es)] {url}")
    print(f"     {content_preview}...")
    print()

Output:

Search results for 'best pizza':
----------------------------------------
  1. [2 match(es)] reviews.com
     Best pizza in town five stars amazing...

  2. [2 match(es)] menu.com
     Margherita pizza pepperoni pizza calzone...

  3. [1 match(es)] specials.com
     Tuesday special two for one pizza deal...

  4. [1 match(es)] homepage.com
     Welcome to our pizza restaurant...

What to Notice

  • Scoring uses the accumulator pattern: each page starts at 0, and every time a query word appears on that page, the score increases by 1. reviews.com scores 2 because it contains both "best" and "pizza"; menu.com also scores 2, but only because it says "pizza" twice. Our simple scorer can't tell those two situations apart — keep that in mind for Discussion Question 4.
  • Ties are broken by URL. results.sort(reverse=True) sorts the (score, url) pairs by score first, then by URL — in reverse alphabetical order, since reverse=True applies to both. That's why specials.com lands ahead of homepage.com.
  • Multiple loops work together: one loop processes query words, a nested loop processes matching pages, and a final loop displays results.
  • Real search engines use hundreds of ranking signals (page authority, freshness, user location, click-through rates), but the loop-and-accumulate structure is the same.

Google says its search index covers hundreds of billions of web pages. Consider the loops involved:

Operation Approximate Scale Loop Type
Crawling 130+ trillion pages known to Google (as of 2016) while (new URLs constantly discovered)
Indexing Hundreds of billions of pages in the index, well over 100,000,000 GB for (iterate over words in each page)
Ranking 5+ trillion searches per year (2025), roughly 14 billion per day for (score each candidate page)
Serving On the order of 150,000 searches per second, on average for (format and return results)

The fundamental patterns are exactly what you learned in this chapter: while loops for open-ended processing, for loops for iterating over collections, and the accumulator pattern for building results. The difference is scale — and the engineering required to run these loops across thousands of servers simultaneously.

Discussion Questions

  1. The web crawler uses a while loop because it doesn't know in advance how many pages exist. Could you use a for loop instead? What would be the limitation?

  2. The crawler has two guards against crawling a page more than once: the continue check at the top of the loop, and the if link not in visited_pages check before adding a link to the queue. What would happen if both checks were removed? (Hint: homepage.com and menu.com link to each other.) Draw a connection to the concept of infinite loops from Section 5.9.

  3. The inverted index maps words to pages. Why is this structure useful for searching? What would happen if the search engine had to scan every page's content for every query instead of using an index? (Hint: think about the difference between searching 100 billion pages vs. looking up a word in a table.)

  4. The ranking algorithm counts word matches — a simple accumulator. But "pizza pizza pizza pizza" on a page would score higher than a thoughtful review that mentions "pizza" once. What additional factors might a real search engine consider? How would you modify the accumulator to account for them?

  5. The crawler generates its own work: each page it visits may add new URLs to the queue. Can you think of other programs that generate their own work as they run? (This pattern is related to what you'll learn about recursion in Chapter 18 and queues in Chapter 20.)

Mini-Project

Extend the search engine simulation: 1. Add at least 5 more "pages" to the simulated web with realistic content. 2. Modify the ranking to give bonus points if the search word appears in the first 10 words of a page's content (a rough proxy for relevance). 3. Add a while loop that lets the user search repeatedly until they type "quit". 4. Track which pages are most frequently returned in search results (another accumulator).

References

  • Google's "How Search Works" documentation provides a high-level overview of crawling, indexing, and ranking: https://www.google.com/search/howsearchworks/ (Tier 1)
  • Brin, S. & Page, L. (1998). "The Anatomy of a Large-Scale Hypertextual Web Search Engine." Computer Networks and ISDN Systems, 30(1-7), 107-117. The original Google paper. (Tier 1)
  • Index size: Google, "Organizing information — How Google Search works": https://www.google.com/search/howsearchworks/how-search-works/organizing-information/ (Tier 1)
  • Known pages (130 trillion, 2016): Search Engine Land, "Google's search knows about over 130 trillion pages": https://searchengineland.com/googles-search-indexes-hits-130-trillion-pages-documents-263378 (Tier 2)
  • Search volume (5 trillion+ per year, disclosed 2025): Search Engine Land, "Google now sees more than 5 trillion searches per year": https://searchengineland.com/google-5-trillion-searches-per-year-452928 (Tier 2). Per-day and per-second figures are simple averages derived from the annual total.