Skip to content
← All posts

Mini Parser: Stack-Based Nested List Deserializer

4 min read
leetcodeproblemmediumstringsstacks

Mini Parser is LeetCode 385, rated Medium. You are given a string like "[123,[456,[789]]]" and you need to parse it into a NestedInteger data structure where each element is either a plain integer or a list of other NestedIntegers.

If you have ever written a JSON parser or dealt with recursive grammars, this will feel familiar. The trick is that you do not need actual recursion. A stack handles the nesting naturally.

The key insight

Every time you see a [, you are entering a new nested list. Every time you see a ], you are closing one. A stack lets you track which list you are currently building. When a list closes, you pop it and tuck it inside the list that sits one level above.

The characters between brackets are either digits (possibly with a leading -) that form numbers, or commas that separate elements. Commas and closing brackets both tell you "the current number is done, finalize it."

input string[123,[456,[789]]]cursorstack (top to bottom)NI(list: [456])depth 1NI(list: [123])depth 0processedcurrentupcoming
Parsing "[123,[456,[789]]]" midway. The stack holds one NestedInteger list per open bracket. Each ']' pops the top and nests it into the list below.

The approach

Walk through the string character by character and maintain two things: a stack of NestedInteger lists, and a temporary string num to accumulate digit characters.

  1. [ - Push a new empty NestedInteger list onto the stack. You are starting a new nesting level.
  2. Digit or - - Append to num. You are in the middle of reading a number.
  3. , - If num is non-empty, convert it to an integer, wrap it in a NestedInteger, and add it to the list on top of the stack. Reset num.
  4. ] - If num is non-empty, finalize the number the same way as with a comma. Then pop the top list off the stack. If the stack is not empty, add the popped list to the new top. If the stack is empty, you are done - return the popped list.

There is one edge case at the very start: if the string does not begin with [, the entire input is a single integer. Return it directly.

The code

class Solution:
    def deserialize(self, s: str) -> NestedInteger:
        if s[0] != '[':
            return NestedInteger(int(s))
        stack = []
        num = ""
        for ch in s:
            if ch == '[':
                stack.append(NestedInteger())
            elif ch == ']':
                if num:
                    stack[-1].add(NestedInteger(int(num)))
                    num = ""
                top = stack.pop()
                if stack:
                    stack[-1].add(top)
                else:
                    return top
            elif ch == ',':
                if num:
                    stack[-1].add(NestedInteger(int(num)))
                    num = ""
            else:
                num += ch
        return stack[0]

Step 0: See '['

Push a new empty NestedInteger list onto the stack.

[123,[456,[789]]]

Stack (top first):

NI(list: [])

The outer bracket opens. We start building the outermost list.

Step 1: Read '1','2','3'

Accumulate digits into num = "123".

[123,[456,[789]]]

Stack (top first):

NI(list: [])

We collect characters into a number string. No stack change yet - we finalize the number when we hit a comma or bracket.

Step 2: See ','

num is "123". Create NI(123) and add it to stack top. Reset num.

[123,[456,[789]]]

Stack (top first):

NI(list: [123])

The comma tells us the current number is complete. We wrap 123 in a NestedInteger and add it to the list on top of the stack.

Step 3: See '['

Push a new empty NestedInteger list onto the stack.

[123,[456,[789]]]

Stack (top first):

NI(list: [])NI(list: [123])

A nested bracket opens a new list. The stack now has two levels of nesting.

Step 4: Read '4','5','6'

Accumulate digits into num = "456".

[123,[456,[789]]]

Stack (top first):

NI(list: [])NI(list: [123])

Same pattern: collect digits, wait for a delimiter to finalize.

Step 5: See ','

num is "456". Add NI(456) to stack top. Reset num.

[123,[456,[789]]]

Stack (top first):

NI(list: [456])NI(list: [123])

456 gets added to the inner list (the top of the stack).

Step 6: See '['

Push a new empty NestedInteger list onto the stack.

[123,[456,[789]]]

Stack (top first):

NI(list: [])NI(list: [456])NI(list: [123])

Third nesting level. The stack now has three open lists.

Step 7: Read '7','8','9'

Accumulate digits into num = "789".

[123,[456,[789]]]

Stack (top first):

NI(list: [])NI(list: [456])NI(list: [123])

Collecting the deepest number.

Step 8: See first ']'

num is "789". Add NI(789) to top. Pop top list, add it to new top.

[123,[456,[789]]]

Stack (top first):

NI(list: [456, [789]])NI(list: [123])

The ']' finalizes 789 and closes the innermost list [789]. That list gets popped and nested inside the middle list, which now holds [456, [789]].

Step 9: See second ']'

No pending num. Pop top list, add it to new top.

[123,[456,[789]]]

Stack (top first):

NI(list: [123, [456, [789]]])

The second ']' closes the middle list. [456, [789]] gets popped and nested inside the outermost list.

Step 10: See third ']'

No pending num. Pop top list. Stack is empty, so return it.

[123,[456,[789]]]

Stack (top first):

stack: empty

The final ']' closes the outermost list. The stack is empty, so we return this NestedInteger as the result: [123, [456, [789]]].

Complexity

TimeSpace
TotalO(n)O(n)

Time: O(n). You scan each character exactly once. Every push, pop, and add operation is O(1), so total work scales linearly with the length of the input string.

Space: O(n). The stack depth equals the maximum nesting level of the input. In the worst case (something like [[[[...]]]]), that could approach n/2. The num accumulator is at most O(n) for a very long number. Both are O(n).

Building blocks

Stack-based nesting

The core pattern here is using a stack to handle recursive structure without actual recursion. Each open delimiter pushes a new context. Each close delimiter pops a context and merges it into the parent.

This is the same pattern you see in:

  • Basic Calculator - parentheses push and pop evaluation contexts
  • Evaluate Reverse Polish Notation - the stack holds intermediate results waiting for operators
  • Decode String (3[a2[bc]]) - brackets push and pop string-building contexts
  • Valid Parentheses - the simplest version, where you just push openers and pop on closers

Once you recognize "nested structure processed left to right," reach for a stack.

Number accumulation

The other small pattern is building a number character by character. You collect digits into a string and finalize the number when you hit a non-digit delimiter. This shows up in any parsing problem: string-to-integer, calculator evaluation, IP address restoration. The key is knowing when to flush the accumulator.

Edge cases

  • Single integer. "324" has no brackets. The very first check (s[0] != '[') catches this and returns immediately.
  • Negative numbers. "[-1]" is valid. The - character falls into the else branch and gets appended to num, so int(num) correctly handles it.
  • Empty nested list. "[[]]" is valid. The inner ] pops an empty NestedInteger list and adds it to the outer one. No number is finalized because num is empty.
  • Deeply nested single values. "[[[5]]]" works fine. Each ] pops a list and nests it into the one below.
  • Multiple commas in a row never happen. The problem guarantees valid input, so you do not need to handle "[1,,2]".

From understanding to recall

Mini Parser is a clean example of the stack-based nesting pattern. The code is short - about 15 lines - but the details matter. When do you flush num? On commas and closing brackets. What do you do after popping? Check if the stack is empty. What about the single-integer edge case? Handle it before the loop.

These are the kinds of things that slip away after a week. You understand the algorithm right now, but reproducing it from memory under interview pressure is a different skill. Spaced repetition locks in those decision points. You write the parser from scratch at increasing intervals until the flow is automatic: check for bare integer, loop through characters, push on [, flush and pop on ], flush on ,, accumulate otherwise. After a few cycles, the code writes itself.

Related posts