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
whileloop 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
forloop iterates over the links found on each page, adding undiscovered ones to the queue. continueis used to skip pages that have already been visited, preventing infinite loops (imagine two pages that link to each other).- 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', '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 three pages: homepage.com, menu.com, and specials.com. The index captures this automatically.
- Two nested
forloops: 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):
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. [1 match(es)] homepage.com
Welcome to our pizza restaurant...
2. [1 match(es)] menu.com
Margherita pizza pepperoni pizza calzone...
3. [1 match(es)] reviews.com
Best pizza in town five stars amazing...
4. [1 match(es)] specials.com
Tuesday special two for one pizza deal...
What to Notice
- Scoring uses the accumulator pattern: each page starts at 0, and for each query word found on that page, the score increases by 1.
- 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.
The Scale of Real Search
Google reportedly crawls and indexes hundreds of billions of web pages. Consider the loops involved:
| Operation | Approximate Scale | Loop Type |
|---|---|---|
| Crawling | ~130 trillion known URLs | while (new URLs constantly discovered) |
| Indexing | ~100 billion words per day | for (iterate over words in each page) |
| Ranking | ~8.5 billion queries per day | for (score each candidate page) |
| Serving | ~99,000 queries per second | 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
-
The web crawler uses a
whileloop because it doesn't know in advance how many pages exist. Could you use aforloop instead? What would be the limitation? -
The crawler skips already-visited pages using
continue. What would happen if this check were removed? Draw a connection to the concept of infinite loops from Section 5.9. -
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.)
-
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?
-
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)
- Search engine scale statistics are widely reported; approximate figures here are based on published Google and internet measurement data as of 2024. (Tier 2)