← Coinbase Interview Insights

Coinbase·Software Engineer·Onsite - Coding / Algorithms·Intermediate

IntermediatePrefer not to say
Jun 2026

Summary

Coinbase software engineer round that was basically one long iterator design problem with layers added on top. No IDE, no autocomplete, just you and a blank editor writing interfaces and tests from scratch. More involved than I expected for a single session.

Questions Asked (3)

Q1

Without using any built-in library types, define an iterator interface from scratch with at least hasNext() and next(), then implement two concrete classes that use it with different traversal behaviors.

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

I started with the interface fine, that part felt clean.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by defining a generic Iterator interface with hasNext() and next() methods, then implement two concrete iterators that traverse different data structures (e.g., a list and a binary tree) with distinct traversal orders. Clearly explain the design choices, such as handling edge cases and ensuring type safety, and discuss trade-offs like time/space complexity and fail-fast behavior.

Pro tip: Mention that you would make the iterator fail-fast by checking for concurrent modification (e.g., using a modCount) to prevent undefined behavior, and discuss how this mirrors Java's Iterator design—showing you understand production-grade robustness.

1. Define the Iterator Interface

Create a generic interface with hasNext() and next() methods, and optionally a remove() method. Explain that hasNext() returns a boolean and next() returns the next element, throwing an exception if no more elements exist.

2. Implement a List Iterator

Implement a concrete iterator for an array or linked list that traverses elements in order. Track the current index and ensure hasNext() checks bounds, while next() returns the element and advances the index.

3. Implement a Tree Iterator

Implement a second iterator for a binary tree that performs in-order traversal using a stack to simulate recursion. Explain how hasNext() checks if the stack is non-empty and next() pops and processes nodes.

4. Discuss Edge Cases and Trade-offs

Cover edge cases like empty collections, single-element collections, and concurrent modification. Discuss trade-offs such as memory usage (stack vs. index) and time complexity (amortized O(1) for next()).

5. Summarize and Relate to Coinbase

Summarize the design, emphasizing modularity and adherence to the Iterator pattern. Relate it to Coinbase's need for robust, scalable systems where custom iterators can efficiently process large datasets.

Key Points to Mention

  • Generic type parameter for type safety (e.g., Iterator<T>)
  • Exception handling: NoSuchElementException when next() is called without hasNext()
  • Fail-fast behavior using modCount to detect concurrent modification
  • Time and space complexity: O(1) amortized for next(), O(h) space for tree iterator
  • Difference between iterator and iterable, and how to make a collection iterable
  • Real-world use cases: processing streams of data, lazy evaluation, and memory efficiency

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

Q2

On top of your iterator implementations, satisfy three additional requirements added one at a time during the interview, such as a peek() method, a filtering wrapper, or flattening multiple iterators together, while keeping each operation efficient.

Algorithms & Data StructuresSystem DesignTechnical Trade-offs
Author's notes

The incremental part is what got me.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the iterator interface and constraints, then implement each requirement incrementally, ensuring each addition preserves efficiency. For each new requirement, discuss trade-offs (time vs. space) and choose the optimal data structure or algorithm. Test with edge cases and explain how the design scales.

Pro tip: Demonstrate awareness of real-world constraints: mention that in production, you'd consider thread-safety, resource cleanup, and backpressure, but for this exercise, focus on algorithmic efficiency and clean abstractions.

1. Clarify requirements and constraints

Ask about the iterator's expected behavior, input types, and performance goals (e.g., O(1) per operation). Confirm whether modifications should be backward-compatible.

2. Design the base iterator

Implement a standard iterator with hasNext() and next(), using a simple data structure (e.g., array, list, or stream) and ensuring O(1) amortized time per operation.

3. Add peek()

Implement peek() by caching the next element or using a one-element buffer. Ensure it doesn't advance the iterator and maintains O(1) time.

4. Add filtering wrapper

Create a wrapper that skips elements not matching a predicate. Use lazy evaluation: advance to the next valid element only when needed, keeping O(1) amortized time per next().

5. Add flattening multiple iterators

Implement flattening by maintaining a queue or stack of iterators. When one is exhausted, move to the next. Ensure O(1) amortized time per element and handle empty iterators gracefully.

Key Points to Mention

  • Time and space complexity for each operation (e.g., O(1) for peek, O(1) amortized for filter and flatten).
  • Lazy evaluation to avoid unnecessary computation and memory usage.
  • Handling edge cases: empty iterators, null elements, infinite iterators.
  • Trade-offs between different implementations (e.g., caching vs. recomputing).
  • Composability: how wrappers can be stacked and interact.
  • Testing strategy: unit tests for each requirement and integration tests for combinations.

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

Q3

Write unit tests from scratch covering each iterator and each added requirement, including edge cases like empty collections, single elements, and calling next() after the iterator is exhausted.

Algorithms & Data Structures
Author's notes

I actually felt okay here.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the iterator interface and requirements, then outline a test plan that covers each iterator and requirement. Write tests using a structured pattern (e.g., Arrange-Act-Assert) and include edge cases like empty collections, single elements, and calling next() after exhaustion. Use a testing framework like JUnit or pytest and ensure tests are independent and readable.

Pro tip: Use parameterized tests to efficiently cover multiple edge cases and avoid duplication. Also, test that calling next() after exhaustion throws the expected exception (e.g., NoSuchElementException) and that hasNext() returns false consistently.

1. Understand the Iterator and Requirements

Review the iterator implementation and any added requirements (e.g., filtering, peeking). Identify the expected behavior for normal and edge cases.

2. Set Up Testing Framework

Choose a testing framework (e.g., JUnit, pytest) and set up the test class. Ensure you can easily create instances of the collection and iterator.

3. Write Tests for Each Iterator and Requirement

For each iterator type and requirement, write tests that verify correct iteration, order, and any special behavior (e.g., skipping elements).

4. Cover Edge Cases

Add tests for empty collections, single-element collections, and calling next() after exhaustion. Verify exceptions and hasNext() behavior.

5. Review and Refactor

Ensure tests are independent, readable, and cover all paths. Use parameterized tests to reduce duplication and improve maintainability.

Key Points to Mention

  • Test each iterator type (e.g., forward, reverse, filtered) separately.
  • Verify that next() returns elements in the correct order and throws NoSuchElementException when exhausted.
  • Test hasNext() returns false after the last element and remains false.
  • Include tests for empty collections: hasNext() should return false immediately, and next() should throw.
  • Include tests for single-element collections: hasNext() true then false, next() returns element then throws.
  • Use parameterized tests to cover multiple edge cases efficiently.

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