Implement a same-host web crawler
Company: Anthropic
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates a candidate's competency in graph traversal and state management, URL parsing and validation, duplicate detection and cycle handling, and concurrent coordination with per-host rate limiting.
Part 1: Crawl All Reachable URLs on the Same Hostname
Constraints
- 0 <= len(link_map) <= 10^4
- Each adjacency list may contain duplicate URLs, cycles, malformed URLs, and cross-host links.
- A valid URL must contain both a scheme and a hostname.
- Only exact hostname matches count; compare by parsed hostname, not by string prefix.
- You do not need to normalize paths, fragments, or query parameters.
Examples
Input: ('http://news.com', {'http://news.com': ['http://news.com/about', 'http://news.com/about', 'http://other.com/x'], 'http://news.com/about': ['http://news.com', 'http://news.com/contact'], 'http://news.com/contact': []})
Expected Output: ['http://news.com', 'http://news.com/about', 'http://news.com/contact']
Explanation: The crawler stays on news.com, ignores the duplicate about link, ignores other.com, and handles the cycle back to the start page.
Input: ('https://site.com', {'https://site.com': ['not a url', '/relative', 'https://site.com/page'], 'https://site.com/page': ['https://site.com', 'mailto:test@example.com', 'https://site.com/help'], 'https://site.com/help': []})
Expected Output: ['https://site.com', 'https://site.com/help', 'https://site.com/page']
Explanation: Malformed and relative links are ignored. Only valid same-host URLs are returned.
Input: ('not a url', {'not a url': ['http://a.com']})
Expected Output: []
Explanation: Edge case: the start URL itself is malformed, so nothing can be crawled.
Input: ('http://solo.com', {})
Expected Output: ['http://solo.com']
Explanation: Edge case: the start URL is valid but has no outgoing links and is missing from the map.
Hints
- Use a visited set so cycles and duplicate links do not cause repeated work.
- Parse the hostname from each URL; checking whether a string starts with the same prefix is not reliable.
Part 2: Simulate a Concurrent Web Crawler with Per-Host Rate Limits
Constraints
- 1 <= workers <= 50
- 0 <= rate_limit <= 10^4
- 0 <= total number of URLs discovered/fetched <= 10^4
- The sum of all link-list lengths can be up to 2 * 10^5.
- A valid URL must contain both a scheme and a hostname.
- Treat two URLs as the same page only if their full strings are exactly equal.
Examples
Input: (['http://a.com', 'http://b.com'], {'http://a.com': ['http://a.com/1', 'http://b.com/1'], 'http://b.com': ['http://b.com/2', 'http://a.com/1'], 'http://a.com/1': [], 'http://b.com/1': [], 'http://b.com/2': []}, 2, 2)
Expected Output: (['http://a.com', 'http://a.com/1', 'http://b.com', 'http://b.com/1', 'http://b.com/2'], [(0, 0, 'http://a.com'), (0, 1, 'http://b.com'), (2, 0, 'http://a.com/1'), (2, 1, 'http://b.com/1'), (4, 0, 'http://b.com/2')])
Explanation: The two seed URLs start in parallel at time 0. Each host must wait 2 time units before another fetch on the same host can start.
Input: (['http://x.com'], {'http://x.com': ['http://x.com/a', 'http://x.com/b', 'http://x.com/a'], 'http://x.com/a': [], 'http://x.com/b': []}, 2, 0)
Expected Output: (['http://x.com', 'http://x.com/a', 'http://x.com/b'], [(0, 0, 'http://x.com'), (1, 0, 'http://x.com/a'), (1, 1, 'http://x.com/b')])
Explanation: With rate_limit = 0, the same host can start multiple fetches at the same time once the links are discovered. Duplicate discovery of /a is ignored.
Input: (['not a url', 'https://good.com'], {'https://good.com': ['/relative', 'https://good.com/p'], 'https://good.com/p': ['bad://']}, 1, 1)
Expected Output: (['https://good.com', 'https://good.com/p'], [(0, 0, 'https://good.com'), (1, 0, 'https://good.com/p')])
Explanation: Malformed seeds and malformed discovered links are ignored. The valid page on good.com is fetched, then its child is fetched one time unit later.
Input: ([], {}, 3, 5)
Expected Output: ([], [])
Explanation: Edge case: no seed URLs means there is nothing to crawl.
Hints
- Think in terms of events: fetch completions and host cooldown expirations both change what can run next.
- Use heaps to manage the next free worker, the next completion time, and the lexicographically smallest eligible URL.