Summary
Meta system design round for a software engineering role. The problem was a web crawler, which sounds manageable until you start thinking through all the moving parts at scale. Left feeling like I covered the basics but probably undersold some of the trickier bits.
Questions Asked(6)
I started with the URL frontier and BFS-style expansion, which felt right, but I stalled a bit when they pushed on prioritization.
Suggested Approach
Start by clarifying scale requirements and constraints (pages per second, storage, freshness needs), then design the system top-down covering URL frontier management, distributed fetching, parsing, and storage. Explicitly call out trade-offs at each layer — such as politeness vs. throughput and BFS vs. priority-based crawling — to demonstrate engineering maturity.
Clarify Requirements & Estimate Scale
Ask about target pages per second (e.g., 1B pages/month ≈ ~400 pages/sec), freshness requirements, scope (entire web vs. specific domains), and whether recrawling is needed. Derive storage estimates — average page ~100KB means ~100TB for 1B pages.
Define High-Level Architecture
Sketch the core pipeline: Seed URLs → URL Frontier → Fetcher Workers → HTML Parser → Content Store + URL Extractor → back to Frontier. Identify that this is a distributed, asynchronous system with multiple decoupled components communicating via queues.
Deep-Dive the URL Frontier
Explain how the frontier must enforce politeness (one request per domain per N seconds), prioritization (PageRank, freshness signals), and deduplication (Bloom filter or distributed hash set). Discuss using a two-tier queue — a priority queue feeding per-domain back queues — to balance throughput and politeness.
Design the Fetcher & Parser Layer
Describe a pool of distributed fetcher workers that respect robots.txt, handle retries with exponential backoff, and use DNS caching to reduce latency. After fetching, parsers extract links, canonicalize URLs, and detect duplicate content using hashing (e.g., SimHash for near-duplicates).
Address Storage, Fault Tolerance & Trade-offs
Discuss storing raw HTML in distributed blob storage (e.g., S3-equivalent) with metadata in a distributed DB, and using Kafka or similar for inter-component communication to handle backpressure. Explicitly call out trade-offs: crawl breadth vs. depth, freshness vs. cost, and centralized vs. distributed frontier.
Key Points to Mention
Bloom filter was my first answer and they seemed fine with it.
Suggested Approach
Frame your answer around the trade-offs between memory efficiency, speed, and accuracy when detecting duplicates at web-scale, covering both URL-level and content-level deduplication. Start with the simplest approach and progressively introduce more scalable solutions, explaining why each upgrade is necessary. Tie your design choices back to real-world constraints like billions of URLs, distributed systems, and latency requirements.
Define the Scale and Constraints
Clarify the expected number of URLs (e.g., billions), acceptable false positive rate, memory budget, and whether the system is distributed. This grounds your solution in realistic engineering trade-offs rather than theoretical ideals.
URL Normalization First
Before any deduplication logic, normalize URLs by canonicalizing schemes, lowercasing hostnames, removing default ports, sorting query parameters, and stripping fragments. This eliminates trivial duplicates caused by syntactic differences pointing to the same resource.
Choose the Right Data Structure for URL Deduplication
Walk through the progression from a HashSet (simple but memory-heavy) to storing URL hashes (MD5/SHA-256) to a Bloom Filter for memory-efficient probabilistic deduplication. Explain the false positive trade-off of Bloom Filters and how to tune the bit array size and hash functions.
Handle Content-Level Deduplication
Explain that different URLs can serve identical or near-identical content, so after fetching a page, compute a fingerprint using SimHash or MinHash to detect near-duplicates. Store these fingerprints in a distributed store and compare against them before scheduling further crawls.
Distributed Coordination and Recrawl Policy
Describe how in a distributed crawler, a centralized URL frontier (e.g., backed by Redis or a distributed queue) with consistent hashing ensures no two workers crawl the same URL simultaneously. Also address recrawl scheduling using TTL-based expiry or change-frequency signals to revisit pages that may have updated content.
Key Points to Mention
This is where I got a bit tangled.
Suggested Approach
Start by decomposing the problem into two distinct concerns — rate limiting enforcement and robots.txt compliance — then explain how each is handled in a distributed context where multiple crawler nodes may target the same domain. Ground your answer in concrete data structures and coordination mechanisms (e.g., Redis, consistent hashing, centralized policy stores) before discussing trade-offs.
Define the Scope and Constraints
Clarify the scale (number of domains, crawl rate targets, number of crawler nodes) and whether rate limits are self-imposed politeness policies or externally mandated. This sets the stage for why naive per-node solutions fail and why coordination is necessary.
Centralized Rate Limiting with Domain Affinity
Explain using a shared rate-limit store (e.g., Redis with token bucket or sliding window counters keyed by domain) combined with consistent hashing or a URL frontier that assigns each domain to a specific crawler shard. This prevents multiple nodes from hammering the same domain independently.
robots.txt Fetching, Parsing, and Caching
Describe fetching robots.txt once per domain at crawl start and caching it (e.g., in a distributed cache or the frontier metadata store) with a reasonable TTL (e.g., 24 hours). The frontier or a pre-dispatch filter checks each URL against the cached rules before enqueuing it for download.
Frontier Design and Politeness Queues
Introduce a per-domain back queue with a configurable delay between requests (e.g., 1–5 seconds), enforced by a scheduler that only releases the next URL for a domain after the delay has elapsed. This naturally enforces crawl-delay directives from robots.txt and prevents thundering-herd issues.
Failure Handling and Trade-offs
Address edge cases: what happens if the robots.txt fetch fails (fail-open vs. fail-closed policy), how to handle rate-limit signals like HTTP 429 responses (exponential backoff, dynamic throttling), and the trade-off between crawl throughput and server politeness.
Key Points to Mention
Froze for a second here.
Suggested Approach
Frame your answer around a multi-signal scoring system that combines static importance signals (PageRank, domain authority, inbound links) with dynamic freshness signals (change frequency, last-modified headers, user engagement). Explain how you'd use a priority queue or tiered scheduling system to balance these competing factors at scale. Ground your answer in real-world constraints like crawl budget, bandwidth, and politeness policies.
Define the Objective
Clarify that the goal is to maximize the utility of the crawl budget by ensuring the most important and most stale pages are crawled first. Acknowledge the tension between importance (high-value pages) and freshness (frequently changing pages).
Establish Importance Signals
Describe static signals such as PageRank, inbound link count, domain authority, and user engagement metrics (click-through rates, dwell time) to score a page's relative importance. Explain how these signals can be pre-computed and stored to avoid real-time computation overhead.
Model Freshness and Change Rate
Use historical crawl data to estimate each page's change frequency using models like a Poisson process, and factor in HTTP signals like Last-Modified and ETag headers. Pages with high change rates and high importance should receive the highest crawl priority.
Design the Priority Scoring System
Combine importance and freshness into a unified priority score, for example: Priority = α × ImportanceScore + β × StalenessScore, where staleness grows as time since last crawl increases. Use a distributed priority queue (e.g., backed by a heap or a system like Kafka with priority lanes) to schedule URLs accordingly.
Handle Operational Constraints
Address crawl politeness (rate limiting per domain via robots.txt and crawl delays), crawl budget allocation across domains, and feedback loops where crawl results continuously update priority scores. Discuss how to handle newly discovered URLs with no historical data using cold-start heuristics.
Key Points to Mention
Pretty standard.
Suggested Approach
Start by clarifying the scale requirements (e.g., billions of pages, petabytes of data) and then propose a tiered storage architecture that separates raw content from structured metadata. Justify each technology choice by mapping it to specific access patterns, consistency requirements, and cost constraints relevant to a company like Meta.
Clarify Scale & Access Patterns
Establish concrete numbers — billions of URLs, petabytes of raw HTML, read/write ratios, and latency SLAs. Distinguish between frequent metadata lookups versus infrequent full-content retrievals to drive storage decisions.
Separate Raw Content from Metadata
Propose storing raw page content (HTML, images) in a distributed object store like Amazon S3 or HDFS, while keeping structured metadata (URL, crawl timestamp, content hash, status codes) in a scalable database. This separation optimizes cost and query performance independently.
Choose the Right Metadata Store
Recommend a wide-column store like Apache Cassandra or HBase for metadata, using the URL or a hashed URL as the partition key to ensure even distribution. Discuss indexing strategies for common query patterns such as recrawl scheduling or domain-level aggregations.
Address Deduplication & Versioning
Use content hashing (e.g., SHA-256 or SimHash for near-duplicates) to avoid storing redundant page content and to detect changes between crawl cycles. Store multiple versions of metadata with timestamps to support historical analysis and change detection.
Plan for Reliability & Scalability
Discuss replication factors, geo-distribution for fault tolerance, and a caching layer (e.g., Memcached or Redis) for hot metadata records. Mention a message queue (e.g., Kafka) to decouple ingestion from storage writes and handle traffic spikes gracefully.
Key Points to Mention
Leaned on a persistent job queue with acknowledgment-based delivery so unfinished URLs get retried.
Suggested Approach
Frame your answer around the core principles of idempotency, durable state management, and work queue design to ensure no URLs are lost or double-processed when workers fail. Walk through the lifecycle of a crawl task from assignment to completion, identifying failure points and the mechanisms that handle each one. Conclude by discussing trade-offs between consistency guarantees and throughput.
Define the Failure Modes
Start by enumerating what 'crash mid-crawl' means: worker process dies, network partition, OOM kill, or slow worker that appears dead. Scoping failure modes shows systematic thinking and sets up your solution cleanly.
Durable Work Queue with Visibility Timeouts
Explain using a persistent, distributed queue (e.g., Kafka, SQS, or a Redis-backed queue) where tasks are not deleted upon dequeue but instead hidden for a visibility timeout period. If a worker crashes before acknowledging completion, the task automatically reappears for another worker to claim.
Idempotent URL Processing
Describe maintaining a distributed 'seen' or 'completed' set (e.g., using a distributed hash set in Redis or a database) so that if a URL is re-processed after a worker crash, the duplicate work is safely detected and skipped without corrupting state.
Checkpointing and State Persistence
Discuss persisting crawl progress — such as partially parsed pages or discovered child URLs — to durable storage before acknowledging task completion, ensuring that a crash mid-parse doesn't lose discovered links. Only mark a task complete after all child URLs are enqueued.
Monitoring, Dead-Letter Queues, and Retry Limits
Explain capping retry attempts per URL and routing persistently failing tasks to a dead-letter queue for manual inspection or alerting, preventing poison-pill URLs from consuming worker capacity indefinitely.
Key Points to Mention
Discussion(6)
Sign in to join the discussion.
Robots.txt and politeness constraints should absolutely be in your opening framing, not a footnote you add when prompted. When I prepped for a similar round, I made the same mistake of treating the URL frontier as the core problem and politeness as a detail. But crawlers that ignore rate limits and crawl directives are genuinely broken systems, not just impolite ones, and interviewers at companies like Meta who run actual large-scale crawl infrastructure know this viscerally. Leading with 'here are the constraints the real world imposes before we even talk about scale' reframes the whole answer and shows you've thought past the textbook BFS expansion.
For the prioritization piece, the honest answer is that PageRank-ish signals are expensive to compute in real time, so in practice you're working with proxy signals: inbound link count from your already-crawled graph, domain authority heuristics, and freshness decay on a per-URL basis. The frontier doesn't have to be a single priority queue either. A tiered structure where you have buckets for high-priority domains, freshness-driven recrawls, and new-discovery URLs lets you tune behavior without recomputing global scores constantly. The tricky part is keeping those tiers from starving each other, which is worth mentioning even if you don't fully solve it in the interview.
Inbound link count and Last-Modified headers are solid anchors, nothing wrong with that answer. The thing that would've elevated it is talking about how the scoring function degrades over time without feedback. If you never revisit a page to see whether your freshness estimate was accurate, you're flying blind. The more interesting design is a lightweight feedback loop where actual observed change rate per domain or URL pattern informs how aggressively you schedule recrawls. Sites that update hourly get treated differently from sites that haven't changed in six months, and you learn that from crawl history rather than assuming it upfront.
Acknowledgment-based delivery from a persistent queue is the right foundation. The exactly-once angle is genuinely hard and most real systems settle for at-least-once with idempotent workers, meaning if a URL gets processed twice the second write just overwrites the first with the same content hash and nothing breaks. Being explicit that you're choosing at-least-once deliberately rather than just not knowing about exactly-once is the difference between sounding like you understand the tradeoff versus sounding like you missed it. The checkpointing piece is mostly about the URL frontier state since the queue handles in-flight work, so clarifying what you're actually checkpointing (frontier snapshots, crawl stats, domain rate limit state) would tighten that part of the answer.
Content hashing for dedup at the body level is the move and you got that right. One thing worth layering in: store the hash alongside the URL mapping so you can answer 'has this content appeared before under a different URL' separately from 'have we visited this URL before.' Those are different questions with different implications for your index downstream.
The TTL refresh for robots.txt is completely defensible, you just have to own it. Something like a 24-hour TTL with a jitter component so you're not stampeding the same domains at the same time is pretty standard. If they push on 'what if it changes mid-crawl and you keep hitting pages that are now disallowed,' the honest answer is you'll have a window of non-compliance and that's an accepted tradeoff in practice. Saying that clearly is better than hedging.
On the distributed rate limiting side, the consistency question is real. A Redis-based token bucket works well when you accept that you might occasionally exceed the limit slightly due to race conditions across workers. For most crawl use cases that's fine, you're not doing financial transactions. The thing worth being crisp on is the key structure: you want per-domain buckets, not per-IP or per-worker, because the target server experiences load at the domain level. One thing I fumbled on a similar question was not distinguishing between rate limiting from the crawler's perspective versus courtesy delays from the target's perspective. They're related but not identical, and showing that distinction tends to land well.
The canonical normalization piece is actually where a lot of real dedup work lives. A bloom filter catches exact string matches, but two URLs pointing to the same page content are a much harder problem. Query param ordering, session tokens, tracking parameters like utm_source, trailing slashes, HTTP vs HTTPS variants, these all produce distinct strings that resolve to identical content. What I'd add to your answer: before any fingerprint hits the bloom filter, run the URL through a normalization pipeline that strips known noise params, lowercases the scheme and host, resolves relative paths, and sorts remaining query params alphabetically. That alone collapses a huge fraction of near-duplicates before you even touch the dedup store. The secondary exact-match store you mentioned for bloom filter false positives is the right call, just be clear it's also your source of truth for 'have we seen this normalized URL before' since the bloom filter can't answer that definitively.