← Coinbase Interview Insights

Coinbase·Software Engineer·Technical Phone Screen·Intermediate

IntermediatePrefer not to say
Apr 2026Remote

Summary

Coinbase software engineering interview that went deep on iterator design in Java, no IDE help allowed, which was a rude awakening for muscle memory. The problem had two parts and a bunch of edge case traps that I only half-anticipated.

Questions Asked (3)

Q1

Design a minimal Iterator interface for integers from scratch (no IDE scaffolding), then implement a FlattenIterator that walks through a list of lists while skipping empty sublists. It must handle edge cases like empty input and repeated hasNext() calls, run in O(1) amortized time, and use O(depth) extra space.

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

I knew the general shape of the answer but fumbled the empty sublist skipping logic on the first pass.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by defining a minimal Iterator interface with hasNext() and next() methods, then implement FlattenIterator using a stack of iterators to handle nested lists. Ensure hasNext() is idempotent and advances past empty sublists, while next() returns the next integer and throws NoSuchElementException when exhausted.

Pro tip: Emphasize that hasNext() must be idempotent and that the stack approach naturally gives O(depth) space; also mention that using an iterator over the outer list avoids index management and simplifies edge cases.

1. Define the Iterator interface

Specify a generic Iterator<T> interface with boolean hasNext() and T next() methods, and optionally a remove() method that throws UnsupportedOperationException.

2. Design FlattenIterator structure

Use a stack (Deque) to store iterators of lists at each level. Initialize by pushing an iterator over the outer list if it's not empty.

3. Implement hasNext() with idempotency

In hasNext(), while the stack is not empty, peek the top iterator; if it has a next element, check if it's a list or integer. If it's an empty list, pop and continue; if it's a non-empty list, push its iterator; if it's an integer, return true. If stack becomes empty, return false.

4. Implement next() using hasNext()

In next(), call hasNext() to ensure an element is available; if not, throw NoSuchElementException. Then pop the top iterator and return its next integer.

5. Analyze complexity and edge cases

Explain that each element is pushed and popped once, giving O(1) amortized time per operation, and the stack depth is at most the nesting depth, giving O(depth) space. Discuss handling empty input, repeated hasNext() calls, and nested empty lists.

Key Points to Mention

  • Idempotent hasNext(): calling it multiple times should not advance the iterator or change state.
  • Stack-based approach: using a stack of iterators naturally handles arbitrary nesting and empty sublists.
  • Amortized O(1) time: each element is processed once, and empty lists are skipped efficiently.
  • O(depth) space: stack size is proportional to the nesting depth, not the total number of elements.
  • Edge cases: empty input, list containing only empty lists, and repeated calls to hasNext() before next().
  • Exception handling: next() should throw NoSuchElementException when no more elements exist.

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

Q2

Implement a FilterIterator that wraps any Integer iterator and only yields elements matching a given predicate. Same robustness requirements: edge cases, no invalid state on repeated hasNext() calls, O(1) amortized time.

Algorithms & Data StructuresSystem Design
Author's notes

This part felt cleaner to me than the flatten problem.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Clarify the interface and requirements, then design a lazy iterator that advances to the next matching element only when needed, caching the result to avoid recomputation. Implement with careful state management to handle edge cases like empty iterators, repeated hasNext() calls, and null predicates, ensuring O(1) amortized time per element.

Pro tip: Mention that you would use a sentinel value or a boolean flag to track whether the next matching element has been computed, preventing redundant work and ensuring idempotent hasNext() calls. Also, discuss how you would test edge cases like an empty source iterator or a predicate that never matches.

1. Clarify Requirements and Interface

Ask about the expected interface (e.g., Java's Iterator<Integer>), whether null elements are allowed, and the predicate's behavior. Confirm that hasNext() must be idempotent and that the iterator should be lazy.

2. Design the State and Lazy Evaluation

Use a reference to the source iterator, the predicate, and a cached next element (or a flag indicating whether it's computed). Implement a private method to advance to the next matching element, updating the cache.

3. Implement hasNext() and next()

In hasNext(), if the cache is not computed, call the advance method; return whether a next element exists. In next(), call hasNext() to ensure the cache is ready, then return the cached element and mark it as not computed.

4. Handle Edge Cases and Robustness

Ensure repeated hasNext() calls do not advance the source iterator. Handle empty source, null predicate (throw NPE), and source iterator throwing exceptions. Consider thread-safety if required.

5. Analyze Complexity and Test

Explain that each element is processed at most once, giving O(1) amortized time per element. Discuss test cases: empty source, no matches, all matches, alternating matches, and large inputs.

Key Points to Mention

  • Lazy evaluation: only advance the source iterator when next() is called or when hasNext() needs to determine if an element exists.
  • Caching the next matching element to ensure hasNext() is idempotent and does not consume elements.
  • Handling edge cases: empty source iterator, predicate that never matches, null elements (if allowed), and null predicate.
  • O(1) amortized time: each source element is examined at most once, and the total work is proportional to the number of elements.
  • State management: using a boolean flag or sentinel to track whether the next element has been computed.
  • Testing strategy: unit tests for various scenarios, including repeated hasNext() calls and interleaved next() calls.

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

Q3

Walk through unit tests for both iterator implementations and explain the time and space complexity of each.

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

Rushed this part a bit.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by briefly describing the two iterator implementations (e.g., array-based and linked-list-based) and their core operations. Then walk through unit tests for each, covering normal cases, edge cases, and error conditions. Finally, analyze the time and space complexity of each operation for both implementations, comparing trade-offs.

Pro tip: Emphasize that unit tests should not only verify correctness but also document expected behavior and catch regressions; mention that complexity analysis should consider amortized costs and worst-case scenarios, which is crucial for financial systems like Coinbase.

1. Describe the iterator implementations

Briefly explain the two iterator types (e.g., array-based and linked-list-based) and their internal state (e.g., index vs. node pointer).

2. Outline unit tests for each

List key test cases: empty iterator, single element, multiple elements, hasNext/next behavior, and exceptions (e.g., NoSuchElementException).

3. Walk through test scenarios

For each test case, describe the setup, action, and expected outcome, highlighting differences between the two implementations.

4. Analyze time complexity

For each operation (hasNext, next), state the time complexity for both implementations, noting any amortized or worst-case nuances.

5. Analyze space complexity

Discuss the space overhead of each iterator, including auxiliary space and any additional data structures used.

Key Points to Mention

  • Unit tests should cover edge cases: empty collection, single element, and iteration beyond bounds.
  • For array-based iterator, hasNext and next are O(1) time; space is O(1) beyond the underlying array.
  • For linked-list iterator, hasNext and next are O(1) time; space is O(1) beyond the list nodes.
  • Time complexity of next may be O(1) amortized if resizing is involved (e.g., dynamic array).
  • Space complexity includes the iterator's own state (e.g., index or pointer) and any temporary storage.
  • Trade-offs: array-based iterators offer fast random access but may be inefficient for insertions/deletions; linked-list iterators are efficient for insertions/deletions but have higher memory overhead per element.

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