Before attempting this problem, you should be comfortable with:
Recursion / DFS - Traversing nested structures of arbitrary depth
Stacks - Simulating recursion iteratively and managing traversal state
Iterator Pattern - Implementing next() and hasNext() methods for lazy evaluation
1. Recursion (Flatten And Store Into Global List)
Intuition
The nested list can contain integers or other nested lists at any depth. We can flatten the entire structure upfront using depth-first traversal. By storing all integers in a flat array during construction, subsequent next() and hasNext() calls become simple array operations.
Algorithm
Constructor: Initialize an empty array and a pointer at 0. Call dfs() on the nested list to populate the array.
dfs(nestedArr): For each element in the nested array:
If it's an integer, append it to the array.
If it's a list, recursively call dfs() on that list.
next(): Return the element at the current pointer and increment the pointer.
hasNext(): Return true if the pointer is less than the array length.
publicclassNestedIterator{privateList<int> arr;privateint ptr;publicNestedIterator(IList<NestedInteger> nestedList){
arr =newList<int>();
ptr =0;Dfs(nestedList);}publicintNext(){return arr[ptr++];}publicboolHasNext(){return ptr < arr.Count;}privatevoidDfs(IList<NestedInteger> nestedArr){foreach(var num in nestedArr){if(num.IsInteger()){
arr.Add(num.GetInteger());}else{Dfs(num.GetList());}}}}
type NestedIterator struct{
arr []int
ptr int}funcConstructor(nestedList []*NestedInteger)*NestedIterator {
it :=&NestedIterator{arr:[]int{}, ptr:0}
it.dfs(nestedList)return it
}func(it *NestedIterator)dfs(nestedArr []*NestedInteger){for_, num :=range nestedArr {if num.IsInteger(){
it.arr =append(it.arr, num.GetInteger())}else{
it.dfs(num.GetList())}}}func(it *NestedIterator)Next()int{
val := it.arr[it.ptr]
it.ptr++return val
}func(it *NestedIterator)HasNext()bool{return it.ptr <len(it.arr)}
classNestedIterator(nestedList: List<NestedInteger>){privateval arr = mutableListOf<Int>()privatevar ptr =0init{dfs(nestedList)}funnext(): Int {return arr[ptr++]}funhasNext(): Boolean {return ptr < arr.size
}privatefundfs(nestedArr: List<NestedInteger>){for(num in nestedArr){if(num.isInteger()){
arr.add(num.getInteger()!!)}else{dfs(num.getList()!!)}}}}
classNestedIterator{privatevar arr:[Int]=[]privatevar ptr:Int=0init(_ nestedList:[NestedInteger]){dfs(nestedList)}funcnext()->Int{let val = arr[ptr]
ptr +=1return val
}funchasNext()->Bool{return ptr < arr.count
}privatefuncdfs(_ nestedArr:[NestedInteger]){for num in nestedArr {if num.isInteger(){
arr.append(num.getInteger())}else{dfs(num.getList())}}}}
Time & Space Complexity
Time complexity: O(n+d)
Space complexity: O(n+d)
Where n is the number of integers and d is the nesting depth.
2. Recursion (Flatten And Return)
Intuition
This is a variation of the first approach where the recursive function returns a flattened list instead of modifying a global variable. Each recursive call builds and returns its own list, which gets merged into the parent's result.
Algorithm
Constructor: Call dfs() on the nested list and store the returned array. Initialize pointer to 0.
dfs(nestedArr): Create an empty result list. For each element:
If it's an integer, append it to the result.
If it's a list, recursively call dfs() and extend the result with the returned list.
Return the result.
next(): Return the element at the current pointer and increment it.
hasNext(): Return true if the pointer is less than the array length.
classNestedIterator:def__init__(self, nestedList):
self.arr = self.dfs(nestedList)
self.ptr =0defdfs(self, nestedArr):
res =[]for num in nestedArr:if num.isInteger():
res.append(num.getInteger())else:
res.extend(self.dfs(num.getList()))return res
defnext(self):
val = self.arr[self.ptr]
self.ptr +=1return val
defhasNext(self):return self.ptr <len(self.arr)
publicclassNestedIterator{privateList<int> arr;privateint ptr;publicNestedIterator(IList<NestedInteger> nestedList){
arr =Dfs(nestedList);
ptr =0;}privateList<int>Dfs(IList<NestedInteger> nestedArr){List<int> res =newList<int>();foreach(var num in nestedArr){if(num.IsInteger()){
res.Add(num.GetInteger());}else{
res.AddRange(Dfs(num.GetList()));}}return res;}publicintNext(){return arr[ptr++];}publicboolHasNext(){return ptr < arr.Count;}}
type NestedIterator struct{
arr []int
ptr int}funcConstructor(nestedList []*NestedInteger)*NestedIterator {
it :=&NestedIterator{ptr:0}
it.arr = it.dfs(nestedList)return it
}func(it *NestedIterator)dfs(nestedArr []*NestedInteger)[]int{
res :=[]int{}for_, num :=range nestedArr {if num.IsInteger(){
res =append(res, num.GetInteger())}else{
res =append(res, it.dfs(num.GetList())...)}}return res
}func(it *NestedIterator)Next()int{
val := it.arr[it.ptr]
it.ptr++return val
}func(it *NestedIterator)HasNext()bool{return it.ptr <len(it.arr)}
classNestedIterator(nestedList: List<NestedInteger>){privateval arr: List<Int>privatevar ptr =0init{
arr =dfs(nestedList)}privatefundfs(nestedArr: List<NestedInteger>): List<Int>{val res = mutableListOf<Int>()for(num in nestedArr){if(num.isInteger()){
res.add(num.getInteger()!!)}else{
res.addAll(dfs(num.getList()!!))}}return res
}funnext(): Int {return arr[ptr++]}funhasNext(): Boolean {return ptr < arr.size
}}
classNestedIterator{privatevar arr:[Int]privatevar ptr:Int=0init(_ nestedList:[NestedInteger]){
arr =NestedIterator.dfs(nestedList)}privatestaticfuncdfs(_ nestedArr:[NestedInteger])->[Int]{var res =[Int]()for num in nestedArr {if num.isInteger(){
res.append(num.getInteger())}else{
res.append(contentsOf:dfs(num.getList()))}}return res
}funcnext()->Int{let val = arr[ptr]
ptr +=1return val
}funchasNext()->Bool{return ptr < arr.count
}}
Time & Space Complexity
Time complexity: O(n+d)
Space complexity: O(n+d)
Where n is the number of integers and d is the nesting depth.
3. Recursion + Stack
Intuition
We can use recursion to flatten the list and store the integers in a stack. By reversing the stack after flattening, we can pop elements in the correct order. This combines recursive traversal with stack-based iteration.
Algorithm
Constructor: Initialize an empty stack. Call dfs() on the nested list, then reverse the stack.
dfs(nested): For each element:
If it's an integer, push it onto the stack.
If it's a list, recursively call dfs() on it.
next(): Pop and return the top element from the stack.
publicclassNestedIterator{privateStack<int> stack;publicNestedIterator(IList<NestedInteger> nestedList){
stack =newStack<int>();List<int> temp =newList<int>();Dfs(nestedList, temp);
temp.Reverse();foreach(var num in temp){
stack.Push(num);}}privatevoidDfs(IList<NestedInteger> nested,List<int> temp){foreach(var num in nested){if(num.IsInteger()){
temp.Add(num.GetInteger());}else{Dfs(num.GetList(), temp);}}}publicintNext(){return stack.Pop();}publicboolHasNext(){return stack.Count >0;}}
type NestedIterator struct{
stack []int}funcConstructor(nestedList []*NestedInteger)*NestedIterator {
it :=&NestedIterator{stack:[]int{}}
it.dfs(nestedList)for i, j :=0,len(it.stack)-1; i < j; i, j = i+1, j-1{
it.stack[i], it.stack[j]= it.stack[j], it.stack[i]}return it
}func(it *NestedIterator)dfs(nested []*NestedInteger){for_, num :=range nested {if num.IsInteger(){
it.stack =append(it.stack, num.GetInteger())}else{
it.dfs(num.GetList())}}}func(it *NestedIterator)Next()int{
val := it.stack[len(it.stack)-1]
it.stack = it.stack[:len(it.stack)-1]return val
}func(it *NestedIterator)HasNext()bool{returnlen(it.stack)>0}
classNestedIterator(nestedList: List<NestedInteger>){privateval stack = ArrayDeque<Int>()init{val temp = mutableListOf<Int>()dfs(nestedList, temp)
temp.reverse()for(num in temp){
stack.addLast(num)}}privatefundfs(nested: List<NestedInteger>, temp: MutableList<Int>){for(num in nested){if(num.isInteger()){
temp.add(num.getInteger()!!)}else{dfs(num.getList()!!, temp)}}}funnext(): Int {return stack.removeLast()}funhasNext(): Boolean {return stack.isNotEmpty()}}
classNestedIterator{privatevar stack:[Int]=[]init(_ nestedList:[NestedInteger]){dfs(nestedList)
stack.reverse()}privatefuncdfs(_ nested:[NestedInteger]){for num in nested {if num.isInteger(){
stack.append(num.getInteger())}else{dfs(num.getList())}}}funcnext()->Int{return stack.removeLast()}funchasNext()->Bool{return!stack.isEmpty
}}
Time & Space Complexity
Time complexity: O(n+d)
Space complexity: O(n+d)
Where n is the number of integers and d is the nesting depth.
4. Stack
Intuition
Instead of flattening everything upfront, we can flatten lazily using a stack. We push the NestedInteger objects themselves onto the stack (in reverse order). When checking hasNext(), we unpack nested lists on demand until we find an integer at the top. This approach is more memory-efficient when we don't need to iterate through all elements.
Algorithm
Constructor: Copy the nested list to a stack in reverse order (so the first element is on top).
next(): Pop the top element and return its integer value.
hasNext(): While the stack is not empty:
If the top element is an integer, return true.
Otherwise, pop the nested list, reverse it, and push its elements back onto the stack.
Where n is the number of integers and d is the nesting depth.
Common Pitfalls
Calling next() Without Checking hasNext() First
The iterator contract requires calling hasNext() before next(). In lazy evaluation approaches, hasNext() is responsible for ensuring an integer is at the top of the stack. Calling next() without first calling hasNext() may return a nested list object instead of an integer, or cause an exception when the stack is empty.
Not Handling Empty Nested Lists
A nested list can contain empty lists like [[]] or [[], [1, []], 2]. Failing to handle these causes hasNext() to return true when no integers remain, or next() to fail. The lazy approach must continue unpacking until it finds an actual integer or the stack becomes empty.
Forgetting to Reverse When Pushing to Stack
When using a stack for lazy evaluation, elements must be pushed in reverse order so the first element ends up on top. Pushing in forward order reverses the iteration order. Similarly, when popping a nested list and pushing its contents, those contents must also be reversed to maintain correct order.