← Adobe Interview Insights

Adobe·Software Engineer·Technical Phone Screen·Intermediate

Intermediate
May 2026

Summary

Adobe SWE interview that went deep on iterator design, specifically building a nested list iterator without pre-flattening. The problem had a lot of moving parts and the complexity analysis comparison at the end felt like a separate mini-interview on its own.

Questions Asked (3)

Q1

Design a lazy iterator over a nested list structure (integers or further lists) that returns integers left-to-right. Implement hasNext() and next() without pre-flattening the input, using O(d) space where d is the max nesting depth.

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

This took me a while to wrap my head around.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Use a stack to simulate the recursion of traversing the nested list, pushing iterators for each level. Maintain the invariant that the top of the stack always points to the next integer to return, advancing through empty lists and nested lists lazily. This achieves O(d) space where d is the maximum nesting depth.

Pro tip: Clarify with the interviewer whether the input is a custom NestedInteger interface or a raw nested list (e.g., List<Object>), and handle both cases gracefully. Also, discuss edge cases like empty lists and null values early to show thoroughness.

1. Clarify the input structure and interface

Ask whether the nested list is represented as a custom NestedInteger class or as a generic List<Object> containing Integers and Lists. Confirm the expected behavior for empty lists and null values.

2. Design the stack-based iterator

Use a stack of iterators (or indices) to track the current position at each nesting level. The stack size will be at most the maximum depth d, ensuring O(d) space.

3. Implement hasNext() to advance to the next integer

In hasNext(), repeatedly check the top of the stack: if it's an integer, return true; if it's an empty list, pop it; if it's a list, push an iterator for that list and continue. This ensures the next call to next() returns the correct integer.

4. Implement next() to return the next integer

Call hasNext() first to ensure the iterator is positioned at an integer. Then, retrieve the integer from the top of the stack, advance the iterator, and return the integer.

5. Analyze complexity and discuss trade-offs

Explain that time complexity is O(n) total for all elements, and space is O(d). Compare with pre-flattening which uses O(n) space, and highlight the advantage of lazy evaluation for large or infinite structures.

Key Points to Mention

  • Stack-based approach to simulate recursion iteratively
  • O(d) space complexity where d is maximum nesting depth
  • Lazy evaluation: only process elements when needed
  • Handling of empty lists and nested empty lists
  • Time complexity: amortized O(1) per next() call, O(n) total
  • Comparison with pre-flattening (O(n) space) and trade-offs

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

Q2

Implement the same nested iterator using a recursive pre-flattening approach, then compare both approaches in time and space complexity.

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

Recursive version was easy to write, honestly felt like a relief after the stack version.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

First, implement the recursive pre-flattening approach by traversing the nested structure and collecting all elements into a flat list, then return an iterator over that list. Next, compare it with the lazy iterator approach (e.g., using a stack) by analyzing time and space complexity, discussing trade-offs in terms of upfront cost, memory usage, and support for infinite or dynamic structures.

Pro tip: Emphasize that the pre-flattening approach is simpler but has O(N) space and time upfront, while the lazy approach is more memory-efficient and can handle infinite sequences, but may have higher per-element overhead. This shows you understand practical engineering trade-offs.

1. Clarify the nested structure and iterator interface

Define the input format (e.g., list of integers and nested lists) and the required iterator methods (e.g., hasNext(), next()). This ensures both implementations adhere to the same contract.

2. Implement recursive pre-flattening

Write a recursive function that traverses the nested structure and appends all elements to a flat list. Then return an iterator over that list, typically using an index pointer.

3. Analyze time and space complexity of pre-flattening

Time: O(N) to traverse and flatten all elements. Space: O(N) for the flat list, plus O(D) recursion stack where D is the maximum depth. Discuss that this is done upfront.

4. Compare with lazy iterator approach

Describe the lazy approach (e.g., using a stack to simulate recursion) which processes elements on demand. Time: O(1) amortized per next() call, O(N) total. Space: O(D) for the stack, where D is depth, not total elements.

5. Summarize trade-offs and use cases

Highlight that pre-flattening is simple but uses O(N) memory and cannot handle infinite structures; lazy is more memory-efficient and supports infinite/large data, but has more complex code and potential per-element overhead.

Key Points to Mention

  • Time complexity: O(N) for both approaches overall, but pre-flattening does all work upfront while lazy spreads it across next() calls.
  • Space complexity: Pre-flattening uses O(N) extra space for the flat list; lazy uses O(D) for the stack, where D is nesting depth.
  • Recursion depth: Pre-flattening recursion depth equals nesting depth, which could cause stack overflow for very deep structures; lazy avoids this by using an explicit stack.
  • Lazy evaluation benefits: Supports infinite or dynamically changing nested structures and is more memory-efficient for large datasets.
  • Implementation simplicity: Pre-flattening is easier to implement and understand, but may be inefficient if only a few elements are needed.
  • Trade-off decision: Choose based on constraints—if memory is limited or data is huge/infinite, prefer lazy; if simplicity and full traversal are needed, pre-flattening is fine.

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

Q3

How would you handle edge cases like empty lists, deeply nested empty lists, and integer boundary values in this iterator?

Algorithms & Data Structures
Author's notes

Caught me a bit flat-footed.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the iterator's contract and the data structure it traverses, then systematically enumerate edge cases across three dimensions: empty containers, nested empties, and integer boundaries. For each, describe the expected behavior, how your implementation handles it, and how you would test it.

Pro tip: Mention that you would write unit tests for these edge cases before or alongside the implementation, and that you'd verify behavior with the interviewer rather than assume—this shows engineering discipline and avoids misalignment.

1. Clarify the iterator's contract

Ask or state what the iterator is supposed to do: what data structure it traverses, what 'next' and 'hasNext' should return, and whether it should skip empty nested lists or treat them as elements.

2. Enumerate edge cases systematically

List edge cases in three categories: empty top-level list, deeply nested empty lists (e.g., [[], [[]], []]), and integer boundary values (Integer.MIN_VALUE, Integer.MAX_VALUE, zero, negatives).

3. Define expected behavior for each case

For each edge case, state what the correct output should be—e.g., hasNext() returns false immediately for empty lists, nested empties are skipped, and boundary integers are returned without overflow.

4. Explain implementation handling

Describe how your iterator's logic (e.g., stack-based or recursive) naturally handles these cases, such as checking for empty collections before pushing and using primitive types to avoid boxing issues.

5. Discuss testing and validation

Mention that you would write unit tests covering each edge case, including stress tests with deep nesting, and verify with the interviewer that your assumptions match the requirements.

Key Points to Mention

  • Empty top-level list: hasNext() should return false and next() should throw NoSuchElementException (or as specified).
  • Deeply nested empty lists: ensure the iterator skips them without infinite loops or stack overflow; use iterative stack or recursion with base case.
  • Integer boundary values: handle Integer.MIN_VALUE and Integer.MAX_VALUE correctly, avoiding overflow in comparisons or arithmetic.
  • Null elements or null nested lists: clarify if they are allowed and handle defensively (e.g., skip or throw).
  • Testing strategy: unit tests for each edge case, including deep nesting and boundary integers.
  • Complexity considerations: ensure edge case handling does not degrade time or space complexity.

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