System designby Learnastra

System-design interview · Core interviews

Design a web crawler

By Anup Rai

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 practice

Useful 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.

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.

  1. Discover eligible URLs. Accept seeds, resolve discovered links in each page’s context and durably schedule eligible new URLs without duplicate initial work.

  2. Fetch and extract. Retrieve supported public HTML, retain verified response bodies, extract article text and publish durable link/document manifests.

  3. Revisit and control sites. Schedule revisits by priority, apply robots and operator pause rules, and record skipped, failed and retryable outcomes.

  4. 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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:

  1. Check origin eligibility and destination safety.
  2. Download within byte/time limits and store body P84.
  3. Save a FETCHED record referring to P84.
  4. Parse that body and write manifest M3 containing extracted text and resolved links U18 and U19.

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.

Design diagramA complete recoverable crawl

Discovered links return to the frontier only through exact eligible URL insertion.

A complete recoverable crawlDiscovered links return to the frontier only through exact eligible URL insertion. seeds to frontier: Eligible seed insertion; frontier to worker: Due leased URL; worker to bodies: Verified bounded response; bodies to parser: Read stored bytes; parser to frontier: Durable discovered URLsEligible seed insertionDue leased URLVerified bounded responseRead stored bytesDurable discovered URLsACTORSeed URLsSTOREDurable frontierSERVICEPolicy-aware fetcherSTOREBody storageSERVICEParser and manifestsyncasync
Read each connection in order
  1. syncEligible seed insertionSeed URLs → Durable frontier
  2. asyncDue leased URLDurable frontier → Policy-aware fetcher
  3. syncVerified bounded responsePolicy-aware fetcher → Body storage
  4. asyncRead stored bytesBody storage → Parser and manifest
  5. 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.

Design diagramSeparate origin scheduling from parsing and indexing

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.

Separate origin scheduling from parsing and indexingOrigin-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. frontier to fetch: Due URL and origin budget; fetch to web: Validated, policy-permitted HTTP; fetch to body: Persist bounded response; fetch to work: Record body reference for parsing; work to parser: Claim parse task; parser to body: Read saved bytes; parser to manifest: Publish extraction and links; manifest to frontier: Discovery consumer inserts exact URLs; manifest to index: Index extracted documentsDue URL and origin budgetValidated, policy-permittedHTTPPersist bounded responseRecord body reference forparsingClaim parse taskRead saved bytesPublish extraction and linksDiscovery consumer insertsexact URLsIndex extracted documentsSTOREOrigin-partitionedfrontier + policySERVICEOutbounddispatchersEXTERNALAllowed publicoriginsSTOREImmutable responsebodiesQUEUEDurable parse tasksSERVICEBounded parserworkersSTOREDocuments + linkmanifestsSTOREDownstream searchindexasyncsyncmedia
Read each connection in order
  1. asyncDue URL and origin budgetOrigin-partitioned frontier + policy → Outbound dispatchers
  2. syncValidated, policy-permitted HTTPOutbound dispatchers → Allowed public origins
  3. mediaPersist bounded responseOutbound dispatchers → Immutable response bodies
  4. asyncRecord body reference for parsingOutbound dispatchers → Durable parse tasks
  5. asyncClaim parse taskDurable parse tasks → Bounded parser workers
  6. mediaRead saved bytesBounded parser workers → Immutable response bodies
  7. syncPublish extraction and linksBounded parser workers → Documents + link manifests
  8. asyncDiscovery consumer inserts exact URLsDocuments + link manifests → Origin-partitioned frontier + policy
  9. 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:

  1. The consumer inserts U18, then crashes.
  2. It repeats the manifest after recovery.
  3. U18’s unique key returns its existing record.
  4. U19 is 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.

Request traceDiscovery resumes after a partial manifest

The cursor advances only after each link is durably present in the frontier.

Discovery resumes after a partial manifestThe cursor advances only after each link is durably present in the frontier. consumer to frontier: Insert U18; consumer to frontier: Crash before U19; consumer to frontier: Retry U18; insert U19; consumer to manifest: Save completed positionPARTICIPANTDiscoveryconsumerPARTICIPANTExact URL frontierPARTICIPANTManifest progress1. Insert U182. Crash before U193. Retry U18; insert U194. Save completed positionsyncblocked
Read each connection in order
  1. syncInsert U18Discovery consumer → Exact URL frontier
  2. blockedCrash before U19Discovery consumer → Exact URL frontier
  3. syncRetry U18; insert U19Discovery consumer → Exact URL frontier
  4. 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.

Foundation · Question 1

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.

What the answer must demonstrate: Distinguishes future work from completed bodies and discovery.

Applied · Question 2

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.

What the answer must demonstrate: Coordinates all workers at the origin and respects site-bound capacity.

Applied · Question 3

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.

What the answer must demonstrate: Allows duplicate network attempts while guarding state updates.

Applied · Question 4

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.

What the answer must demonstrate: Keeps manifest progress behind durable exact URL insertion.

Applied · Question 5

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.

What the answer must demonstrate: Validates actual connection destinations and every redirect.

Applied · Question 6

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.

What the answer must demonstrate: Identifies false-positive recall loss and exact-store authority.

Applied · Question 7

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.

What the answer must demonstrate: Preserves effective-URL context when sharing bytes.

Applied · Question 8

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.

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 lesson
Design 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 lesson
Design 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 lesson

Final 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.

Technical references

Practice marks stay in this browser.