Finite Web Crawler
Problem statement
You are given a finite collection of pages. urls[i] is the URL of page i, and links[i] is the ordered list of normalized URLs returned by the provided fetch-and-parse helpers for that page.
Starting from startUrl, crawl the reachable pages in breadth-first order:
- Discover
startUrlfirst. - When a discovered page is processed, inspect its links from left to right.
- If a link exactly matches a URL in
urlsand that page has not been discovered, discover it and schedule it for processing. - Ignore links that are not present in the finite collection.
Return the URLs in discovery order. Discover each page at most once. The finite collection, rather than a hostname restriction, defines the crawl boundary.
Follow-up: Concurrent crawling
Explain how you would run fetch-and-parse calls concurrently while scheduling each page at most once. Discuss atomic discovery, a bounded worker pool, and how to detect completion when workers may still discover more pages. If the same discovery order must be preserved, explain how you would separate parallel fetching from ordered scheduling.
This follow-up is a design discussion. The judged function still receives the finite graph above and returns the specified breadth-first discovery order; it does not perform network requests or create worker threads.
Function
crawlPages(urls: String[], links: String[][], startUrl: String) → String[]Examples
Example 1
urls = ["https://a.test/start","https://a.test/docs","https://b.test/news","https://a.test/end"]links = [["https://a.test/docs","https://b.test/news","https://missing.test/page"],["https://a.test/end"],["https://a.test/end","https://a.test/start"],[]]startUrl = "https://a.test/start"return = ["https://a.test/start","https://a.test/docs","https://b.test/news","https://a.test/end"]The start page discovers the docs page and the cross-domain news page in that order. The missing page is outside the finite collection. The docs page then discovers the end page; later links to already discovered pages do not add them again.
Example 2
urls = ["https://site.test/root","https://site.test/a","https://site.test/b","https://site.test/isolated"]links = [["https://site.test/a","https://site.test/a"],["https://site.test/b"],["https://site.test/root"],["https://site.test/root"]]startUrl = "https://site.test/root"return = ["https://site.test/root","https://site.test/a","https://site.test/b"]The duplicate link to a and the cycle back to root are ignored after discovery. The isolated page is present in the collection but is not reachable from the start page.
Example 3
urls = ["https://x.test/alpha","https://x.test/beta","https://x.test/start","https://x.test/zeta"]links = [[],["https://x.test/alpha"],["https://x.test/beta","https://outside.test/no-copy"],["https://x.test/start"]]startUrl = "https://x.test/start"return = ["https://x.test/start","https://x.test/beta","https://x.test/alpha"]The start page need not be first in urls. The outside link is ignored because it has no supplied page, and zeta is not reachable.
Constraints
1 ≤ urls.length ≤ 5,000.links.length = urls.length.- Every value in
urlsis a unique normalized absolute HTTP or HTTPS URL. startUrlappears exactly once inurls.- Every entry in
linksis a normalized absolute HTTP or HTTPS URL and may be absent fromurls. - The total number of entries across all
links[i]is at most100,000. - URL equality is exact and case-sensitive; no redirects, fragments, or additional canonicalization are performed.