Back

Web Crawler

Phone ScreenPhone ScreenLast reported September 2026High Frequency

Problem Overview

Where you'll code it
ReplitCodeSignal
Live shared coding environment. You may need to create test files / inputs yourself. A correct run produces 100 URLs.
Full problem statement

The phone-screen coding question of 2025, reported in forty-eight threads from January 2025 to September 2026, and the one most often paired with the recruiter's "be familiar with handling concurrency" line. You are given a starting URL and a helper that fetches a page and returns the URLs on it: candidates name it getURLs, getLinksFrom, get_url, getLinks, htmlParser, or "a jsoup wrapper", and several confirm it is provided, not written by you. The task, as two candidates state it: write a web crawler that only visits pages on the same hostname as the start URL; strip the #fragment from a URL before deduplicating, so http://example.com/page#a and http://example.com/page#b are one page; visit each page once; no other URL normalization is required. One interviewer refused to read or explain the prompt and let the candidate work it out; another steered a candidate who wanted to start concurrent back to a single-threaded version first.

Part 1 — single-threaded crawler: a plain BFS (one candidate: "BFS, done in seconds"). The code is run against a real small website; one report says the result is correct when it has fewer than 100 URLs, another counted 55. The tests on this page call crawl_single(startUrl, htmlParser) with a mock parser that records how many pages it fetched, and accept any iterable of the visited URLs.

Part 2 — concurrent crawler: the follow-up in nearly every report: make it faster. Most candidates wrote a multi-threaded version (thread pool, ThreadPoolExecutor, "multi-thread"); several wrote asyncio, two compared or benchmarked the runtimes, and one interviewer asked how a task gets processed the moment it is ready. One candidate: the concurrent version "is mainly about making visited thread-safe so the same URL is never fetched twice". The tests call crawl_threaded(startUrl, htmlParser, max_workers=16) and check that the result matches Part 1 and that every page is fetched exactly once. Browsers cannot start threads, so the tests swap in a synchronous stand-in for ThreadPoolExecutor: each submit runs its job to completion before returning. That means a lock-ordering race or a drain loop that waits only once cannot be caught here; those two bugs only show with real threads, so run it locally with a real pool as well.

The spoken follow-ups, from the reports that describe them: thread pool versus asyncio versus hand-rolled threads and their trade-offs (one candidate felt the interviewer was waiting for the word "threadpool"; another found the asyncio direction welcome); coroutine questions; how to spread the crawl over a cluster and assign URLs to machines, or scale to a million URLs; and, in a London system-design variant of the question, politeness toward the crawled servers and near-duplicate pages.

Notes: Replit was the platform in early 2025 (slow, import errors on run, five minutes lost for one candidate), CodeSignal since mid-2025. Python dominates the reports; one non-Python candidate wrote threads directly, and two 2026 replies say Python candidates now get the image-processing question as Q1 while other languages still get the crawler. One interviewer told a candidate who ran out of time before the threaded part that "most people don't get to the multi-threaded part"; that candidate was rejected. One candidate who rewrote the provided synchronous parser as an async one hit a page their parser could not decode and had no time to fix it.

Follow-up Arc

Interviewers escalate through these phases. The order varies, but most candidates see at least one from each bucket.
Concurrency · 1Distributed scale · 1Edge cases · 1Trade-off discussion · 4
Concurrency

Thread pool, asyncio or hand-rolled threads, and why?

Probes for: after the concurrent version; one candidate was asked to compare manual threads, thread pool and asyncio
Distributed scale

How would you crawl on a cluster and assign URLs to machines, or scale to a million URLs?

Probes for: after the concurrent version; one candidate proposed a central Redis queue handing batches to workers
Edge cases

Our crawler is too aggressive; how would you be polite to the servers? Many URLs point to the same or similar content; how would you handle that?

Probes for: a London system-design variant of the question
Trade-off discussion

How would you make it faster?

Probes for: after the single-threaded crawler runs; most reports: a multi-threaded version, written and run

How do you make sure a task is processed the moment it is ready?

Probes for: in the multi-threaded discussion, one round

How would you compare the two versions' performance?

Probes for: two candidates benchmarked or compared runtimes

Coroutine questions

Probes for: after a thread-pool version, one round

Approach Trade-offs

Approaches actually attempted in reports — including ones that lost candidates time. Pick deliberately.
ApproachNotes
ThreadPoolExecutorWhat most reports describe; wraps the provided blocking helper without rewriting it; the GIL is released during I/O.
asyncio with the helper in asyncio.to_thread or run_in_executorSeveral candidates' route; one passed steering toward it; the helper must not be awaited directly.
asyncio with aiohttp and your own parserOne candidate tried it and their parser failed on a page's encoding; high risk under time pressure.
Cluster of crawlersSpoken follow-up only: partition URLs by host across machines; one candidate proposed a central Redis queue handing batches to workers.

What Reports Emphasize

Common mistakes: Deduplicating before stripping the fragment, or explaining the steps in the wrong order: one candidate knew the rule and was still asked repeatedly where the sanitising happens.; Running out of time before the concurrent version: one interviewer told such a candidate that most people do not get there, and that candidate was rejected.; Rewriting the provided synchronous parser as an async one: one candidate's parser could not decode a page (0x89) and the round ended there; their advice was a thread pool.; Awaiting the synchronous helper directly, so the async version is no faster: a replier's diagnosis for a candidate who saw no speedup.; Listing many concurrency primitives before the one you will use: one candidate mentioned gevent and felt nothing landed until 'threadpool'.; Not running the code, or losing time to the environment: the result is checked against a real site (fewer than 100 URLs), and one candidate spent five minutes on Replit import errors.

Interviewer hints: The recruiter's prompt in many reports: be familiar with handling concurrency using primitives or libraries of your choice; in 2026 the same prompt also led to file deduplication, the LRU cache and image processing.; One interviewer insisted on the single-threaded crawler before any concurrent version.; One interviewer would not read or explain the prompt and left it to the candidate.; One interviewer asked how a task gets processed as soon as it is ready; another asked for the trade-offs between manual threads, a thread pool and asyncio.; One candidate felt the interviewer was waiting for the word 'threadpool'; another prepared asyncio and found the conversation went that way.

What passers do: Write the single-threaded BFS, run it against the site, then make it concurrent and run that too; one candidate who did both, with a runtime comparison, still got no positive comment and was not advanced, so expect no feedback either way.; Strip the fragment before the visited check and say so in that order.; Use urlparse for hostnames and URL handling (one candidate's advice) and a thread pool or asyncio.to_thread around the provided helper rather than a rewritten parser.; One candidate who passed the phone screen and heard the next day had prepared the asyncio version deliberately and steered the follow-up toward it.

Why people fail: Not reaching the concurrent version in time (one confirmed rejection, with the interviewer's remark that most people do not).; An async rewrite of the parser that could not decode a page, with no time left.; Coding slowly or getting stuck on a detail of the BFS: one candidate's improved version was never run before time ran out.

Edge cases probed: URLs with #fragments pointing at the same page.; Links to other hostnames that must be skipped.; A URL discovered by two pages at once in the concurrent version (fetch it once).; A real site with fewer than 100 reachable pages (one count: 55).

Practice

Write your own against 10 test cases, or read the worked solution — approach, complexity, and code that runs.

Prepping for Anthropic with friends?

Send them this page. It is free to read, no account needed.

More Anthropic Questions

Free preview

Every question in the Anthropic catalog gets this depth

What you just read — canonical solution, follow-up arc, what passing candidates actually did — exists for all 49 Anthropic questions, refreshed monthly from new candidate reports.

$59/mo — or $50/mo with the 3-month pass · cancel anytime
Anthropic · Phone Screen · Reported 48× across candidate reports
Is this helpful?