System-design interview · Core interviews
Design a web crawler
Build a durable, polite crawl pipeline that can repeat work after failures without losing discovered links or turning arbitrary URLs into unsafe network access.
You will learn to
- Trace a URL through durable scheduling, fetching, parsing and discovery.
- Explain why origin policy and destination safety constrain achievable throughput.
- Separate exact URL membership from body deduplication and approximate filters.
Practice in this chapter
8 interview questions with model answers and follow-ups.
Go to interview practiceUseful foundations: Message queues, event logs, delivery guarantees, and backpressure · Database indexes: B-trees, composite keys and query access · Data partitioning and sharding · Production readiness: SLI, SLO, observability, and recovery
Workload and timing examples are interview assumptions.
Dotted concept links open the relevant explanation in a new tab.
01Define a finite crawl objective
Design a crawler for a public engineering-article search corpus. It accepts seed URLs, downloads eligible HTML, extracts article text and links, and revisits important pages on a schedule. Fetching U17 discovers U18 and U19; both must eventually enter the durable work queue if eligible.
The web is changing and can generate unlimited addresses, so “crawl everything” is not a useful completion condition. Choose a target corpus, site budgets and freshness classes, such as daily revisits for important articles and monthly revisits for low-priority pages. Authenticated content, bypassing restrictions and unrestricted media downloads are outside this exercise.
An origin is a scheme, hostname and port. Enforce configured per-origin concurrency and spacing, and honor robots.txt rules for the crawler’s identified user agent. Robots rules express crawl policy, not permission to access private networks or bypass authentication. A site pause or throttling response can reduce throughput regardless of spare machines.
The promise is recoverable processing, not exactly-once HTTP retrieval. A network timeout may leave the crawler unsure whether a server received the request, so retrying can fetch the same page twice. The design must make repeated scheduling and output processing safe instead of claiming the network eliminates duplication.
Clarify the target corpus and deadline, which pages need revisiting, and what each site permits. If site budgets cannot support the requested completion rate, renegotiate the deadline instead of treating more fetch workers as a solution.
02Functional requirements
Agree on these supported actions before selecting components.
Discover eligible URLs. Accept seeds, resolve discovered links in each page’s context and durably schedule eligible new URLs without duplicate initial work.
Fetch and extract. Retrieve supported public HTML, retain verified response bodies, extract article text and publish durable link/document manifests.
Revisit and control sites. Schedule revisits by priority, apply robots and operator pause rules, and record skipped, failed and retryable outcomes.
Resume interrupted stages. Recover pending fetch, parse and discovery work after a worker crash without skipping links whose scheduling never committed.
03Non-functional requirements
Use these as illustrative interview assumptions to agree with the interviewer. Numerical targets require measurement; they are not claims about an existing product or a proven implementation.
Finite throughput objective. Target fifteen billion eligible initial pages in 28 days: approximately 6,200 successful new pages/s and 620 MB/s at the assumed 100 KB/page. Retries and revisits require additional capacity; the initial-crawl arithmetic does not pay for them.
Politeness before speed. For the worked example allow at most one start per two seconds and one active fetch per origin, or a stricter site/operator limit. At least 12,400 continuously eligible origins are needed for the initial start rate even before retries and revisits.
Revisit freshness. Use daily revisits for important articles and monthly revisits for low-priority pages when site policy permits. Track overdue eligible work separately from policy-blocked pages, and revise the service objective when permissions or capacity prevent it.
Recoverable work and bytes. Accepted frontier entries and committed stage outputs must survive worker restart or one storage-node failure within the region. Repeated HTTP retrieval is allowed; lost committed discovery work is not.
Destination and input safety. Only fetch allowed public HTTP(S) destinations, including after DNS resolution and redirects. Enforce response/decompression/time/parser limits and do not access authenticated or internal resources.
Bounded pipeline. Keep ready batches, sockets and parser work bounded. Slow intake when downstream stages fill; useful coverage and revisit age matter more than an unbounded raw request rate.
04Make one worker restartable before adding more
Start with one scheduler/worker, a SQL frontier and durable body storage. The frontier is the collection of URLs waiting to be fetched or revisited. Each row records the canonical URL, state, due time and current attempt. A transaction selects a due URL and marks it leased to the worker for a bounded time.
For U17, the fetch-and-parse path is:
- Check origin eligibility and destination safety.
- Download within byte/time limits and store body
P84. - Save a
FETCHEDrecord referring toP84. - Parse that body and write manifest
M3containing extracted text and resolved linksU18andU19.
A manifest is durable output that a later process can finish consuming after a crash.
A discovery step inserts U18 and U19 into the frontier under unique URL keys. It records its manifest progress after those insertions complete. Only then is that discovery work finished. The stages distinguish “bytes obtained” from “all discovered work durably scheduled.”
If the process crashes after storing P84 but before recording it, a retry may download again and leave an unused object to clean up. If it crashes after recording FETCHED, processing can continue from P84 without another network request. One visited=true flag cannot express these different recovery actions.
Discovered links return to the frontier only through exact eligible URL insertion.
Read each connection in order
- syncEligible seed insertionSeed URLs → Durable frontier
- asyncDue leased URLDurable frontier → Policy-aware fetcher
- syncVerified bounded responsePolicy-aware fetcher → Body storage
- asyncRead stored bytesBody storage → Parser and manifest
- asyncDurable discovered URLsParser and manifest → Durable frontier
05Check site permission before worker count
Assume fifteen billion eligible pages over 28 days, averaging 100 KB each and one second of network service time. These are planning assumptions, not permission to fetch any particular site at that rate.
| Quantity | Calculation | Consequence |
|---|---|---|
| Fetch rate | 15 billion / (28 × 86,400) | About 6,200 pages/s. |
| Download payload | 6,200 × 100 KB | About 620 MB/s. |
| Raw retained bytes | 15 billion × (100 KB + 500 B metadata) | 1.5075 PB before copies. |
| Space at 70% occupancy | 1.5075 PB / 0.7 | About 2.15 PB before replicas. |
| Typical concurrent fetches | 6,200/s × 1 second | About 6,200 active requests. |
| Link candidates | Ten links/page × 6,200 | About 62,000 membership checks/s before deduplication. |
At one allowed start every two seconds per origin, sustaining 6,200 starts/s needs at least 12,400 continuously eligible independent origins. One thousand such origins allow only about 500 starts/s. Adding workers cannot remove this constraint.
At 200 bytes per frontier record, one billion pending URLs require 200 GB before indexes and replicas. Keep durable state on storage and only bounded ready batches in memory. Slow-request tails increase connection occupancy beyond the one-second average, so measure timeouts and outstanding sockets as well as successful pages/s.
06Store URLs, fetches and bodies as different identities
| Record | Responsibility |
|---|---|
| URL/frontier row | Canonical address, provenance, priority, next due time, state and current attempt. |
| Origin schedule | Robots/pause policy, next permitted start and active-fetch limit. |
| Fetch result | Status, effective URL after redirects, timestamps, selected headers and body reference. |
| Body object | Immutable downloaded bytes and integrity digest. |
| Parse manifest | Extraction version, text and discovered links awaiting consumption. |
Canonicalization converts only safe equivalents, such as removing a fragment that is not sent in an HTTP request. Do not drop every query parameter: two article URLs may legitimately differ by a query value. Preserve the full canonical URL, since a hash cannot reconstruct the address to fetch.
Use a unique exact canonical-URL key for discovery. In the baseline, inserting that row also creates its pending frontier work because the frontier is an index over the same table. If separate queue storage is introduced, save an enqueue intention in the URL transaction and relay it afterward; otherwise a crash between “known” and “queued” can strand the address forever.
Keep successful fetch history separate from revisit scheduling. “Already known” means do not create duplicate discovery work, not “never fetch this URL again.” A changed article must become due under its freshness policy.
07Coordinate requests at the origin boundary
All workers fetching the same origin share one scheduler decision. Before a start, check the current site policy, reserve an active slot and advance nextAllowedStart atomically. One worker’s local sleep does not coordinate another worker’s request to the same site.
Choose one outbound dispatcher per origin partition. It owns the active connections and enforces the configured concurrency and spacing. Parallelism comes from many independent origins. If a dispatcher fails, stop and confirm that its old outbound requests have ended before transferring that origin’s work. The simple interview design accepts delayed crawling during uncertain handoff rather than claiming that a new database token can close an old machine’s socket.
Fetch and cache robots.txt through a standards-compliant implementation. Recheck policy when its cache is no longer valid, and conservatively pause ordinary fetches if policy cannot be established. Handle HTTP throttling and transient failures with delayed retries, not an immediate tight loop. A disallowed URL is a recorded skipped outcome, not a network error to retry aggressively.
Origin names can share the same physical server. Add conservative host/operator budgets where needed and expose a manual pause. The product’s four-week target must be revised if its eligible sites cannot support the required polite rate.
08Treat every destination and response as untrusted
An attacker can place a link that makes the crawler contact an internal service the attacker cannot reach directly. This is server-side request forgery. Reject non-HTTP(S) schemes and private/internal destinations, including unsafe IPv4 and IPv6 results. Validate the address actually used by the connection, not only a preliminary DNS lookup that the HTTP client later resolves differently.
Pin an approved resolved address through connection establishment while preserving the correct HTTP host and TLS hostname. Revalidate every redirect target and new resolution. A public starting URL does not make its redirect to an internal address safe. Cached Domain Name System results may reduce lookup work, but they still need an allowed lifetime and safety validation.
Limit URL length, redirects, response bytes, decompressed size, fetch duration and parser CPU/memory. A compressed response can expand far beyond its transferred size. Sandbox parsing and verify the content type before selecting an HTML handler; a filename extension is not sufficient.
Crawl traps include endless calendars, sort/filter combinations and session URLs. Bound per-site discovery and repeated path patterns, and quarantine explosive origins for inspection. These are coverage decisions as well as resource safeguards: stopping a trap allows useful sites to remain fresh.
09Separate network waiting from parsing work
Partition the frontier by origin so each scheduler owns the timing of its assigned sites. Use asynchronous HTTP workers with bounded global and per-origin connections. They can wait on many unrelated servers without allocating an unbounded thread or process to each request.
As traffic grows, fetchers persist body references and enqueue parsing work through durable stage records. Separate parser workers consume those records, extract documents and links, and publish manifests. This isolates CPU-heavy or malformed HTML from network scheduling and lets a parser fix reprocess retained bytes without downloading again.
Object storage absorbs the large sequential body volume. A search index downstream consumes extracted documents under its own indexing-freshness contract; a fetched page is not automatically searchable. Make each stage slow its input when it cannot keep up; this is backpressure. If parsing or object storage is full, slow new downloads instead of filling local disks or losing bodies already acknowledged as fetched.
Ready queues can be small in-memory batches derived from durable due-time indexes. Losing a batch then delays work rather than deleting it. Replicate and back up frontier/manifest state, and test restoring both pending URLs and partially consumed discovery output. A hash-based placement scheme distributes ownership; it does not supply durability by itself.
For the selected one-node-loss boundary, retain frontier and manifest decisions through durable majority commits across three replicas in independent regional failure domains, with body storage whose acknowledged writes survive one storage-node loss. Budget retries and revisits above the approximately 6,200/s initial-crawl rate; reduce intake or renegotiate the deadline when site budgets or downstream capacity cannot support the combined work.
Origin-owned dispatchers select due URLs and enforce site policy before fetching. They persist bodies and durable parse work; parsers publish manifests containing documents and discovered links. Discovery consumers insert exact URLs into the frontier, while downstream indexing has its own freshness boundary. Full later stages slow new fetches.
Read each connection in order
- asyncDue URL and origin budgetOrigin-partitioned frontier + policy → Outbound dispatchers
- syncValidated, policy-permitted HTTPOutbound dispatchers → Allowed public origins
- mediaPersist bounded responseOutbound dispatchers → Immutable response bodies
- asyncRecord body reference for parsingOutbound dispatchers → Durable parse tasks
- asyncClaim parse taskDurable parse tasks → Bounded parser workers
- mediaRead saved bytesBounded parser workers → Immutable response bodies
- syncPublish extraction and linksBounded parser workers → Documents + link manifests
- asyncDiscovery consumer inserts exact URLsDocuments + link manifests → Origin-partitioned frontier + policy
- asyncIndex extracted documentsDocuments + link manifests → Downstream search index
10Distinguish repeated addresses from repeated bytes
The exact URL store prevents two pages discovering U18 from scheduling duplicate initial work. A body digest answers a different question: did two fetches return identical bytes? Reusing an identical object can save storage and some parsing, but URL provenance and site policy remain separate.
Identical HTML can contain the relative link ./2 on two different sites:
| Response’s effective URL | Relative link | Resolved destination |
|---|---|---|
https://a.example/docs/1 |
./2 |
https://a.example/docs/2 |
https://b.example/docs/1 |
./2 |
https://b.example/docs/2 |
Those bytes resolve to different destination URLs. Cached syntax-level parsing can be reused where valid, but link resolution must still use each response’s effective URL and any valid HTML base element.
A Bloom filter is a compact membership accelerator. Its negative result means an entry was not inserted into that complete filter; a positive result means possibly present. False positives are possible, so a positive cannot be the sole reason to discard a newly discovered URL when coverage matters. Consult the exact store and let its uniqueness constraint decide insertion. A stale or incomplete filter cannot replace that final check either.
Use sufficiently strong digests with integrity or collision verification for body reuse. Digest equality is a useful lookup candidate, not a reason to erase all URL-specific records. Keep the canonical address, fetch time, redirects and source page even when bytes are shared.
11Recover the stage that actually committed
A lease gives one worker the current right to update a URL attempt for a bounded period. Completion includes its attempt token. If worker A pauses and worker B receives a new attempt, A’s late completion is rejected by a conditional database update. That protects state; the separate outbound-dispatch policy protects network politeness.
For discovery, suppose M3 contains U18 and U19:
- The consumer inserts
U18, then crashes. - It repeats the manifest after recovery.
U18’s unique key returns its existing record.U19is inserted next.
Advance the manifest cursor only after durable insertion or confirmed membership. Marking U17 visited before saving or consuming its links could permanently lose U19.
If a body object exists but no stage references it, cleanup must first make the associated attempt ineligible to publish before deleting it. A check-then-delete race could otherwise remove bytes just as a worker records FETCHED. Cleanup must keep bodies and manifests still referenced by retained processing stages.
Classify outcomes: transient network errors retry with delay; permanent unsupported content stops; policy disallow skips; repeated parser failures enter an inspectable failed state. Conditional requests may receive 304 Not Modified and reuse the prior valid body; they must not replace it with an empty document.
The cursor advances only after each link is durably present in the frontier.
Read each connection in order
- syncInsert U18Discovery consumer → Exact URL frontier
- blockedCrash before U19Discovery consumer → Exact URL frontier
- syncRetry U18; insert U19Discovery consumer → Exact URL frontier
- syncSave completed positionDiscovery consumer → Manifest progress
12Check the design against the requirements
Use the agreed lists to check the finished design. The tests below still need to establish the targets; a proposed mechanism is not a measured result. FR refers to the numbered functional requirements above; NFR refers to the numbered non-functional requirements.
| Requirement | Design mechanism | Validation and remaining limit |
|---|---|---|
| FR1,4 + NFR4: complete discovery | Exact URL keys, durable manifests and checkpoints after insertion. | Crash after scheduling U18 but before U19; replay must recover U19 without duplicating U18. |
| FR2 + NFR4–5: safe retained bodies | Bounded validated fetches, immutable objects and guarded stage records. | Test redirects to private addresses, decompression bombs and one-node loss after FETCHED acknowledgment. |
| FR3 + NFR1–3: feasible crawl rate | Origin-owned dispatch, due-time scheduling and explicit freshness classes. | Check allowed-origin capacity and revisit/retry overhead against the 28-day plan. Spare workers cannot overcome a site’s policy. |
| NFR4,6: recoverable overload | Durably replicated frontier/manifests, separate parsers and backpressure. | Stop parsing or lose a dispatcher. Keep accepted work recoverable and delay handoff until old outbound requests are known to have ended. |
13Rapid revision
Remember: Save the extracted links before recording discovery progress; a downloaded parent is not proof that its children were scheduled.
Track useful new documents per fetched byte, frontier age, revisit freshness, duplicate ratio, parser backlog, per-origin request starts and skipped reasons. A high fetch rate can hide a calendar trap or thousands of duplicate mirrors. Measure the slowest freshness class and site behavior, not only fleet averages.
| Decision | Reason | Cost or limit |
|---|---|---|
| Save progress for each queued URL | Distinguish downloaded, parsed and fully scheduled work after crashes. | State and checkpoint storage. |
| One shared dispatcher per web origin | Coordinate workers’ request spacing and concurrent fetches. | Site policy bounds throughput. |
| Immutable bodies and manifests | Replay parsing and discovery after crashes. | Petabyte retention and reread cost. |
| Unique keys for canonical URLs | Repeat discovery without duplicate scheduling. | Store URLs and check for existing entries. |
| Reuse bodies with matching digests | Avoid repeated byte storage and some parsing. | Resolve relative links using each page’s URL. |
| Destination and response limits | Prevent internal-network access and resource exhaustion. | Some inputs are deliberately rejected. |
Close by tracing U17 to stored P84, manifest M3 and durably scheduled U18/U19. Explain what happens if the worker fails after each stage. Then state the main scaling boundary: many independent eligible origins provide parallelism, while one site’s permitted rate remains fixed. The interview design favors recoverability and respectful bounded coverage over an impossible claim to fetch an infinite web exactly once.
Practise the interview questions
Say your answer aloud before opening the model answer. Then answer the follow-up and compare the reasoning.
What is the frontier and why must it be durable?
Reveal a model answer
It records URLs waiting for initial fetch or revisit. Losing it loses accepted future work, even if previously fetched bodies survive.
Interviewer follow-up
Why is visited=true inadequate?
Reveal the follow-up answer
It cannot distinguish stored bytes, parsed output and links not yet durably scheduled.
What the answer must demonstrate: Distinguishes future work from completed bodies and discovery.
Why do per-worker delays fail to enforce politeness?
Reveal a model answer
Multiple workers can each obey their own delay while sending concurrent requests to the same origin. Origin-wide state must coordinate starts and active requests.
Interviewer follow-up
What bounds total throughput?
Reveal the follow-up answer
Both worker capacity and the number of independently eligible origins under their site budgets.
What the answer must demonstrate: Coordinates all workers at the origin and respects site-bound capacity.
Can a crawler promise exactly-once HTTP fetches?
Reveal a model answer
No. A timeout can leave the request outcome unknown, and recovery may repeat the network operation. Durable idempotent processing is the defensible promise.
Interviewer follow-up
What prevents late completion from overwriting a newer attempt?
Reveal the follow-up answer
A conditional state update checks the current attempt token.
What the answer must demonstrate: Allows duplicate network attempts while guarding state updates.
How do you recover after inserting U18 but before U19?
Reveal a model answer
Replay the durable manifest. Exact unique URL keys make U18 harmless to repeat, then U19 is inserted before progress advances.
Interviewer follow-up
Why not checkpoint before inserting links?
Reveal the follow-up answer
Recovery would skip links that had never been saved in the frontier.
What the answer must demonstrate: Keeps manifest progress behind durable exact URL insertion.
Why must a redirect be checked again?
Reveal a model answer
Its target may be outside scope or resolve to a private network even when the original address was public.
Interviewer follow-up
Why pin the validated address?
Reveal the follow-up answer
A second unchecked DNS resolution could select a different unsafe destination.
What the answer must demonstrate: Validates actual connection destinations and every redirect.
Why cannot a Bloom positive prove a URL was seen?
Reveal a model answer
False positives can mark an unseen URL as possibly present. Discarding it without exact lookup silently loses coverage.
Interviewer follow-up
Does the exact store still need uniqueness on concurrent inserts?
Reveal the follow-up answer
Yes. The filter is an accelerator, not a concurrency or identity constraint.
What the answer must demonstrate: Identifies false-positive recall loss and exact-store authority.
Can identical HTML skip all link extraction work?
Reveal a model answer
No. Relative links resolve using each page’s effective URL, so the same bytes on two origins can discover different addresses.
Interviewer follow-up
What may be reused?
Reveal the follow-up answer
Stored bytes and context-independent parse results, while preserving per-fetch provenance and link resolution.
What the answer must demonstrate: Preserves effective-URL context when sharing bytes.
What should happen when parsing falls behind?
Reveal a model answer
Keep fetched bodies durable and reduce new intake until the downstream stage catches up. Unbounded downloading only moves the failure to storage.
Interviewer follow-up
What metric reveals productive crawling?
Reveal the follow-up answer
Useful new documents per fetched byte and revisit freshness, not fetch count alone.
What the answer must demonstrate: Uses backpressure and useful-content outcomes rather than raw throughput.
Blank-page exercise · 45 minutes
Build the answer yourself
Design a public article crawler for fifteen billion eligible pages in four weeks. Trace U17 discovering two URLs and recover a crash before the second is scheduled.
- 0–5 min: agree numbered functional and non-functional requirements for corpus, initial deadline, revisits, per-origin policy, recoverability and destination safety.
- 5–12 min: trace durable stages and estimate fetch/network/storage demand.
- 12–20 min: define exact URL state, origin schedule and manifests.
- 20–30 min: scale independent origins and separate fetch from parse.
- 30–38 min: handle partial discovery, stale attempts, unsafe redirects and duplicate bytes.
- 38–45 min: check the final pipeline against the numbered FR/NFR lists, including origin-budget feasibility, revisit/retry capacity, durable discovery and unsafe-input tests.
Check that each component and design decision follows from your requirements and workload.
Recall the key ideas
Answer from memory before opening each card. Explain why the choice works and what it costs. Revisit missed cards tomorrow.
Design a web crawlerU17 produces manifest M3 with U18 and U19. The worker inserts U18, then crashes. What survives and what repeats?Recall first, then reveal
M3 survives and is replayed. U18’s unique key returns its existing frontier row; U19 is inserted next. Save manifest progress only after both are durably scheduled.
Save links, then advance progress.
Return to lessonDesign a web crawlerTwo URLs return identical bytes. Which checks detect repeated URLs and repeated bodies?Recall first, then reveal
The canonical URL identifies an address; the content digest identifies its bytes. Keep each URL’s context even when reusing the body.
Same address is not same bytes.
Return to lessonDesign a web crawlerHow can more workers increase crawl speed without overloading one site?Recall first, then reveal
Fetch from independent origins in parallel. Workers targeting the same origin share its timing and concurrency limits; all fetches still pass destination checks.
Parallel across sites, polite within each.
Return to lessonFinal revision
Summary and interview notes
Save each discovered URL and its progress through fetch, parse and scheduling. Workers resume interrupted stages while shared site limits, robots rules and destination checks constrain what they fetch.
Remember these points
- Make pending work and parse output durable.
- Coordinate politeness across every worker for an origin.
- Validate actual destinations and bound untrusted responses.
- Use exact URL identity and retain URL-specific context during byte deduplication.
Interview tips
- Test a crash after each committed stage.
- Calculate the minimum number of eligible origins, not just worker count.
Important qualifications
- Uncertain dispatcher failover delays crawling until old outbound requests are known to have ended.
- The design does not claim exhaustive or exactly-once coverage of the web.
Continue after the core interview
Explore the advanced version
The advanced lesson keeps the full detailed design. Use these sections when you want to examine the stronger requirements and failure cases.
- Fenced egress ownership during partitions
Prove network-dispatch exclusivity when conservative handoff delay is insufficient.
- Attempt and garbage-collection interleavings
Analyze publication rights, exact membership and object reclamation races.
- Robots retrieval edge cases
Work through protocol-specific caching and failure outcomes.
- Canonicalizer rollout and parsing economics
Measure identity changes, extraction differences and repeated-body processing cost.
Technical references
- RFC 9309: Robots Exclusion ProtocolPrimary specification for robots.txt fetching, matching, and caching behavior.
- Google: faceted-navigation crawlingConcrete examples of URL proliferation and crawl-budget problems.
- OWASP SSRF Prevention Cheat SheetPrimary security guidance on validated network destinations, DNS behavior and redirect bypasses.
Practice marks stay in this browser.