← Google Interview Insights

Google·Machine Learning Engineer·Technical Phone Screen·Senior

SeniorPrefer not to say
Jun 2026

Summary

Google ML Engineer interview that was basically a coding round disguised as a systems question. They asked me to implement a web crawler two ways, then spent a good chunk of time poking at edge cases and design decisions I hadn't fully thought through.

Questions Asked (7)

Q1

Implement a depth-first search web crawler in Python, given a starting URL, a get_links function, and a max_pages limit. The crawler should visit each page at most once and handle cycles.

Algorithms & Data StructuresSystem Design
Author's notes

I went straight to recursion and got partway through before they asked about stack depth.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Clarify requirements and edge cases, then implement an iterative DFS using a stack and a visited set to avoid recursion limits and cycles. Track the number of pages visited and stop when max_pages is reached, ensuring each URL is processed only once.

Pro tip: Mention that you'd use an explicit stack instead of recursion to avoid Python's recursion limit and to make the solution more robust for large crawls. Also, discuss how you'd handle exceptions from get_links to keep the crawler running.

1. Clarify requirements and edge cases

Ask about expected behavior for invalid URLs, network errors, and whether the starting URL counts toward max_pages. Confirm that get_links returns a list of absolute URLs.

2. Choose data structures

Use a stack (list) for DFS traversal and a set for visited URLs to ensure O(1) membership checks. Optionally, use a counter to track pages visited.

3. Implement the DFS loop

Push the start URL onto the stack. While the stack is not empty and pages visited < max_pages, pop a URL, skip if already visited, mark as visited, process the page (e.g., call get_links), and push new links onto the stack.

4. Handle errors and edge cases

Wrap get_links in a try-except to handle exceptions gracefully. Ensure that the visited set is updated before processing to avoid infinite loops. Consider normalizing URLs to avoid duplicates.

5. Analyze complexity and test

Discuss time complexity O(N) where N is number of pages visited, and space complexity O(N) for the stack and visited set. Walk through a simple example to verify correctness.

Key Points to Mention

  • Use an explicit stack to implement DFS iteratively, avoiding recursion depth issues.
  • Maintain a visited set to ensure each page is visited at most once and to handle cycles.
  • Respect the max_pages limit by counting pages as they are visited.
  • Handle exceptions from get_links to prevent the crawler from crashing.
  • Normalize URLs (e.g., remove fragments) to avoid visiting the same page multiple times.
  • Discuss time and space complexity: O(N) time and O(N) space, where N is the number of pages visited.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q2

Implement a breadth-first search version of the same web crawler using a queue, with the same constraints.

Algorithms & Data StructuresSystem Design
Author's notes

This one went smoother.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the constraints and requirements, then outline the BFS algorithm using a queue, emphasizing how it differs from DFS. Discuss implementation details, including visited set, queue operations, and handling of the same constraints (e.g., politeness, depth limits).

Pro tip: Mention that BFS is naturally suited for finding shortest paths and can be parallelized by processing levels concurrently, but be mindful of memory usage due to the queue size. Also, relate it to ML engineering by noting how BFS can be used for feature discovery in graph-structured data.

1. Clarify constraints and requirements

Ask about the specific constraints: maximum depth, politeness policy, domain restrictions, and whether the crawler should be single-threaded or distributed. Confirm the goal is to implement BFS using a queue.

2. Outline BFS algorithm

Describe the BFS approach: initialize a queue with seed URLs, maintain a visited set, and process URLs level by level. For each URL, fetch the page, extract links, and enqueue unvisited links that satisfy constraints.

3. Discuss implementation details

Explain data structures: queue (e.g., collections.deque in Python), visited set (e.g., hash set), and any additional structures for depth tracking. Mention how to handle politeness (e.g., delays) and depth limits.

4. Compare with DFS and address trade-offs

Highlight that BFS uses more memory but finds shortest paths, while DFS is memory-efficient but may go deep. Discuss how BFS can be adapted for distributed crawling by partitioning the queue.

5. Consider scalability and ML relevance

Talk about scaling BFS for large-scale crawling (e.g., using distributed queues like Kafka) and how BFS can be applied in ML contexts, such as exploring graph-structured data for feature engineering.

Key Points to Mention

  • Queue data structure (FIFO) and its role in BFS
  • Visited set to avoid revisiting URLs
  • Handling constraints: depth limit, politeness, domain restrictions
  • Memory considerations and potential for distributed BFS
  • Comparison with DFS: BFS finds shortest paths but uses more memory
  • Application to ML: graph traversal for feature discovery or data collection

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q3

What bugs can appear if you implement the DFS crawler using recursion instead of an explicit stack?

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

Blanked for a second.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by contrasting recursion and explicit stack implementations of DFS, focusing on the call stack's limited depth. Then, systematically discuss potential bugs: stack overflow, increased memory usage, and performance degradation. Conclude with trade-offs and when recursion might still be acceptable.

Pro tip: Mention that recursion depth is bounded by the call stack size, which is typically much smaller than heap memory, so deep crawls can crash the program. Also, note that recursion can obscure the algorithm's state, making debugging harder.

1. Explain DFS and the role of the stack

Briefly describe DFS and how both recursion and explicit stack manage the traversal order. Highlight that recursion uses the call stack implicitly.

2. Identify stack overflow risk

Discuss that deep recursion can exceed the call stack limit, causing a stack overflow crash, especially on large graphs or deep trees.

3. Analyze memory and performance implications

Compare memory usage: recursion adds call overhead and may use more memory per frame than an explicit stack. Also, recursion can be slower due to function call overhead.

4. Consider other bugs and limitations

Mention potential issues like difficulty in pausing/resuming traversal, lack of control over stack size, and increased risk of infinite recursion if cycles are not handled.

5. Summarize trade-offs and recommendations

Conclude that for web crawling, where depth can be large, an explicit stack is safer. Recursion might be fine for small, bounded graphs.

Key Points to Mention

  • Stack overflow due to limited call stack size
  • Higher memory consumption from call stack frames
  • Performance overhead of function calls
  • Difficulty in debugging and tracing recursive calls
  • Lack of flexibility to control traversal depth or pause/resume
  • Potential for infinite recursion if cycles are not properly handled

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q4

How does Python's default recursion stack limit affect a recursive crawler implementation, and how would you work around it?

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

Mentioned sys.setrecursionlimit but immediately said that's a hack.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by explaining that Python's default recursion limit (typically 1000) can cause a RecursionError in deep recursive crawlers, especially when traversing deep website hierarchies. Then discuss practical workarounds such as converting to an iterative approach with an explicit stack, increasing the recursion limit (with caution), or using asynchronous/queue-based crawling. Emphasize trade-offs and why an iterative solution is often preferred in production ML pipelines at scale.

Pro tip: Mention that while you can increase sys.setrecursionlimit(), it risks a C stack overflow and is not a scalable solution; instead, highlight that an iterative BFS/DFS with a deque is more robust and aligns with Google's preference for scalable, production-ready code.

1. Identify the limitation

Explain that Python's default recursion limit is ~1000 and that recursive crawlers can easily exceed this when following deep link chains, leading to RecursionError.

2. Assess impact on crawler

Discuss how this limit affects crawler reliability, causing crashes or incomplete data collection, which is unacceptable in ML data pipelines where completeness matters.

3. Evaluate workarounds

Compare options: increasing recursion limit (quick but risky), converting to iterative with explicit stack/queue (robust), or using asynchronous crawling with bounded concurrency.

4. Recommend best practice

Advocate for an iterative approach using collections.deque for BFS or a list for DFS, which avoids recursion limits and is more memory-efficient and scalable.

5. Consider ML pipeline implications

Tie back to ML engineering: iterative crawlers integrate better with distributed systems (e.g., Apache Beam, Scrapy) and avoid stack overflows in long-running jobs.

Key Points to Mention

  • Python's default recursion limit (sys.getrecursionlimit()) is typically 1000.
  • RecursionError occurs when the limit is exceeded, crashing the crawler.
  • Increasing the limit via sys.setrecursionlimit() can lead to segmentation faults due to C stack overflow.
  • Iterative implementation with an explicit stack (DFS) or queue (BFS) eliminates recursion depth issues.
  • Asynchronous or queue-based crawling (e.g., using asyncio or Scrapy) is more suitable for large-scale web crawling.
  • Trade-offs: recursion is simpler but limited; iteration is more complex but scalable and production-ready.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q5

Why should the visited set store URL strings rather than some mutable object representing a URL?

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

Strings are hashable, mutable objects aren't.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by explaining that URL strings are immutable, hashable, and provide value-based equality, which are essential for correct and efficient set operations. Then contrast with mutable objects that can break hashing and equality, leading to subtle bugs. Finally, tie this to practical concerns like memory, serialization, and distributed processing in ML pipelines.

Pro tip: Mention that in distributed systems like Google's, using immutable strings avoids synchronization issues and makes it easy to serialize/deserialize visited sets across machines, which is crucial for large-scale web crawling or data deduplication.

1. Define the requirements of a set

Explain that a set requires elements to be hashable and support equality comparison to ensure uniqueness and fast lookups.

2. Explain immutability and hashability

Describe how URL strings are immutable, so their hash code remains constant, while mutable objects can change after being added to a set, corrupting the set's internal structure.

3. Discuss equality semantics

Point out that strings have well-defined value-based equality, whereas mutable objects might use identity-based equality or have mutable fields that affect equality, leading to inconsistent behavior.

4. Address practical considerations

Mention that strings are memory-efficient (especially with interning), easily serializable for distributed processing, and avoid the overhead of custom objects.

5. Conclude with trade-offs

Summarize that while a custom immutable URL class could work, strings are simpler, less error-prone, and sufficient for most use cases, especially in ML pipelines where URLs are just identifiers.

Key Points to Mention

  • Immutability ensures hash code stability, preventing set corruption.
  • Strings have value-based equality, which is necessary for correct deduplication.
  • Mutable objects can lead to bugs if modified after insertion into a set.
  • Strings are easily serializable, which is important for distributed systems.
  • Memory efficiency: strings can be interned or stored compactly.
  • Simplicity and reduced overhead compared to custom objects.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q6

How would you prevent bugs caused by shared mutable state if the same crawler instance is reused across multiple runs?

System DesignTechnical Trade-offs
Author's notes

This one caught me a bit.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by acknowledging the risks of shared mutable state in reused crawler instances, then propose a design that isolates state per run or enforces immutability. Discuss trade-offs between performance (e.g., avoiding re-initialization) and correctness, and suggest concrete patterns like dependency injection or state reset hooks.

Pro tip: Emphasize that the safest approach is to avoid reuse altogether unless profiling proves it necessary; if reuse is required, make state explicit and test for idempotency across runs.

1. Identify shared mutable state

Enumerate all mutable fields (e.g., caches, counters, session tokens) that persist across runs and could cause cross-run contamination.

2. Choose an isolation strategy

Decide between full isolation (new instance per run) or controlled reuse with explicit reset/initialization, based on performance needs.

3. Implement state management

Use patterns like dependency injection, immutable data structures, or a reset() method to ensure state is either per-run or safely reinitialized.

4. Enforce with testing and monitoring

Add unit tests that run the crawler multiple times and assert no state leakage, and monitor for anomalies in production.

5. Discuss trade-offs

Compare performance overhead of isolation vs. risk of bugs, and mention how to measure and mitigate the impact.

Key Points to Mention

  • Idempotency and stateless design principles
  • Dependency injection to pass fresh state per run
  • Immutable data structures to avoid accidental mutation
  • Explicit reset or teardown methods for reusable instances
  • Thread-safety and concurrency concerns if runs are parallel
  • Testing strategies like running multiple iterations and checking for state leakage

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q7

How would you extend the crawler to handle timeouts, failed HTTP requests, and concurrent crawling?

System DesignTechnical Trade-offs
Author's notes

Open-ended and I rambled a bit.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the crawler's current architecture and requirements, then systematically address each concern: timeouts, failed requests, and concurrency. For each, propose robust mechanisms (e.g., exponential backoff, retries, async I/O) and discuss trade-offs like throughput vs. politeness, and resource usage. Finally, tie back to ML engineering by highlighting data quality and pipeline reliability.

Pro tip: Emphasize idempotency and observability: ensure retries don't duplicate data and add metrics/logging for failures. This shows you think about production reliability, not just coding.

1. Clarify requirements and constraints

Ask about scale, target sites, politeness policies, and existing infrastructure. This ensures your solutions are appropriate and not over-engineered.

2. Handle timeouts and failed requests

Implement configurable timeouts, retries with exponential backoff and jitter, and circuit breakers for persistent failures. Consider fallback strategies like caching or skipping.

3. Implement concurrency

Use asynchronous I/O (e.g., asyncio, aiohttp) or a distributed task queue (e.g., Celery, Kubernetes jobs) to parallelize requests. Manage concurrency limits to avoid overwhelming targets.

4. Ensure robustness and observability

Add logging, metrics (success/failure rates, latency), and alerting. Design for idempotency to avoid duplicate data on retries.

5. Discuss trade-offs and ML implications

Balance throughput vs. politeness, resource usage vs. speed. Highlight how reliable crawling improves ML data quality and pipeline stability.

Key Points to Mention

  • Exponential backoff with jitter for retries to avoid thundering herd
  • Asynchronous I/O or distributed task queues for concurrency
  • Rate limiting and respect for robots.txt to avoid bans
  • Circuit breakers and dead-letter queues for persistent failures
  • Idempotency and deduplication to prevent data corruption
  • Monitoring and alerting for crawler health and data quality

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.