Build an Exactly-Once Crawler and Add Safe Concurrency

Read the full interview experience this question came from →

Quick Overview

Build a crawler around a supplied fetch-and-parse API, then add bounded concurrency, atomic discovery, correct completion detection, and explicit failure semantics.

Build an Exactly-Once Crawler and Add Safe Concurrency

Company: MongoDB

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: hard

Interview Round: Technical Screen

Implement a web crawler using the supplied operation `fetchAndParse(String url)`, which returns the links on that page. Your entry point is `void crawl(String seedUrl)`: starting from the seed, visit every reachable page exactly once. The method has no result collection to return. First explain a single-threaded implementation. Then identify its bottleneck and design a concurrent version with production-quality synchronization, termination, and failure handling. ### Constraints & Assumptions - The supplied operation performs fetching and link extraction. Do not implement an HTML parser or replace it with a graph-array input. - **Baseline practice assumptions:** the reachable graph is finite and static; links are canonical absolute URL strings; every fetch eventually succeeds; and the supplied operation can be called concurrently. Under these assumptions, exactly once means one call to `fetchAndParse` for each distinct reachable URL, including the seed. - The report gives no same-host restriction, depth limit, page cap, retry contract, or crash-recovery guarantee. Do not silently add a boundary that excludes reachable URLs from the baseline requirement. - For the production discussion, relax the success assumption and explain what retries, timeouts, cancellation, and restart recovery do to the exactly-once claim. Concurrency must be bounded; no fixed worker count or throughput target is supplied. ### Clarifying Questions to Ask - Does a URL string identify a page, or must aliases, fragments, and redirects be canonicalized before claiming uniqueness? - Is `fetchAndParse` safe for concurrent calls, and can it time out or be cancelled? - Does “exactly once” count attempts, successful fetches, or processing of their results when failures occur? - If an operational page limit is necessary, may the crawl report an incomplete outcome rather than claiming all reachable pages were visited? ### Part 1 — Traverse Without Repeating Fetches Describe the frontier, visited state, and placement of the visited check. Explain why the crawler handles self-links, cycles, and several pages linking to the same destination. State when the void method can return. #### What This Part Should Cover - Reserving a URL before it enters the frontier. - Exhausting reachable work under the finite, successful-fetch baseline. - Traversal and state complexity separated from network and parsing cost. ### Part 2 — Add Concurrency Without Losing Work Design a bounded worker architecture. Explain what happens when two workers discover the same URL and when the frontier is temporarily empty while another fetch is still running. #### What This Part Should Cover - An atomic check-and-reserve transition and fetching outside shared locks. - Work accounting that includes queued and active pages. - An enqueue/backpressure policy that cannot deadlock all workers while they publish discovered links. ### Part 3 — Define the Production Failure Contract Explain how a failed fetch is represented, how retries are owned, how the caller learns about incomplete execution, and what happens if the process fails after a request was sent but before its completion was recorded. #### What This Part Should Cover - Separate discovery, attempt, and terminal states without duplicate retry scheduling. - Resource shutdown and cancellation that do not claim successful exhaustive completion. - The boundary between in-process duplicate prevention and durable exactly-once external effects. ```hint Count work beyond the queue A worker that removed the last frontier item may still discover several new URLs. Identify the state that remains nonzero during that fetch. ``` ### What a Strong Answer Covers - A crawler faithful to the supplied I/O API and void entry point. - A precise exactly-once guarantee under the baseline assumptions and explicit weaker guarantees under failures. - Atomic discovery, bounded network concurrency, and correct completion detection without holding a global lock during I/O. ### Follow-up Questions - What breaks if a URL is added to the visited set only after its fetch finishes? - How can a bounded queue deadlock if every worker is also a producer of child URLs? - How would you resume a crawl after a process crash while acknowledging that an uncertain fetch may need to be repeated?

Overview: Build a crawler around a supplied fetch-and-parse API, then add bounded concurrency, atomic discovery, correct completion detection, and explicit failure semantics.

Read the full MongoDB Software Engineer interview experience this question came from

|Home/Software Engineering Fundamentals/MongoDB
MongoDB logo
MongoDB
Aug 9, 2026
hardSoftware EngineerTechnical ScreenSoftware Engineering Fundamentals
2
0

Implement a web crawler using the supplied operation fetchAndParse(String url), which returns the links on that page. Your entry point is void crawl(String seedUrl): starting from the seed, visit every reachable page exactly once. The method has no result collection to return.

First explain a single-threaded implementation. Then identify its bottleneck and design a concurrent version with production-quality synchronization, termination, and failure handling.

Constraints & Assumptions

  • The supplied operation performs fetching and link extraction. Do not implement an HTML parser or replace it with a graph-array input.
  • Baseline practice assumptions: the reachable graph is finite and static; links are canonical absolute URL strings; every fetch eventually succeeds; and the supplied operation can be called concurrently. Under these assumptions, exactly once means one call to fetchAndParse for each distinct reachable URL, including the seed.
  • The report gives no same-host restriction, depth limit, page cap, retry contract, or crash-recovery guarantee. Do not silently add a boundary that excludes reachable URLs from the baseline requirement.
  • For the production discussion, relax the success assumption and explain what retries, timeouts, cancellation, and restart recovery do to the exactly-once claim. Concurrency must be bounded; no fixed worker count or throughput target is supplied.

Clarifying Questions to Ask Guidance

  • Does a URL string identify a page, or must aliases, fragments, and redirects be canonicalized before claiming uniqueness?
  • Is fetchAndParse safe for concurrent calls, and can it time out or be cancelled?
  • Does “exactly once” count attempts, successful fetches, or processing of their results when failures occur?
  • If an operational page limit is necessary, may the crawl report an incomplete outcome rather than claiming all reachable pages were visited?

Part 1 — Traverse Without Repeating Fetches

Describe the frontier, visited state, and placement of the visited check. Explain why the crawler handles self-links, cycles, and several pages linking to the same destination. State when the void method can return.

What This Part Should Cover Guidance

  • Reserving a URL before it enters the frontier.
  • Exhausting reachable work under the finite, successful-fetch baseline.
  • Traversal and state complexity separated from network and parsing cost.

Part 2 — Add Concurrency Without Losing Work

Design a bounded worker architecture. Explain what happens when two workers discover the same URL and when the frontier is temporarily empty while another fetch is still running.

What This Part Should Cover Guidance

  • An atomic check-and-reserve transition and fetching outside shared locks.
  • Work accounting that includes queued and active pages.
  • An enqueue/backpressure policy that cannot deadlock all workers while they publish discovered links.

Part 3 — Define the Production Failure Contract

Explain how a failed fetch is represented, how retries are owned, how the caller learns about incomplete execution, and what happens if the process fails after a request was sent but before its completion was recorded.

What This Part Should Cover Guidance

  • Separate discovery, attempt, and terminal states without duplicate retry scheduling.
  • Resource shutdown and cancellation that do not claim successful exhaustive completion.
  • The boundary between in-process duplicate prevention and durable exactly-once external effects.

What a Strong Answer Covers Guidance

  • A crawler faithful to the supplied I/O API and void entry point.
  • A precise exactly-once guarantee under the baseline assumptions and explicit weaker guarantees under failures.
  • Atomic discovery, bounded network concurrency, and correct completion detection without holding a global lock during I/O.

Follow-up Questions Guidance

  • What breaks if a URL is added to the visited set only after its fetch finishes?
  • How can a bounded queue deadlock if every worker is also a producer of child URLs?
  • How would you resume a crawl after a process crash while acknowledging that an uncertain fetch may need to be repeated?