Stacks and Queues · Stacks and Queues Fundamentals

Recursive Stack Manipulation

Sorting (or otherwise rearranging) a stack using only push/pop/peek/isEmpty and recursion - the call stack itself stands in for the "extra storage" a non-recursive approach would otherwise need.

  • 5 min read
  • 9 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: nested structure
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

Sort a stack using only push, pop, peek and isEmpty - no array, no second stack. It looks impossible until you notice that recursion already gives you a stack: every pending call holds its local variables. Popping into a local variable and pushing back as the calls return is a stack of popped values for free.

This skill covers the pop, recurse, push-back shape that sorts or reverses a stack with nothing but recursion, and the same save-and-restore idea applied to nested text such as "2[x3[yz]]". It also covers when to trade the call stack for an explicit one.

We'll go from simple to more technical:

  1. The pattern: sort a stack by popping, sorting the rest, and inserting in place.
  2. A second flavor: nested structure (Decode String) with explicit stacks of saved state.
  3. Where save-and-restore shows up (parsers, template engines, nested scopes).
  4. What breaks it: unrestored pops, single-digit counts, recursion depth, the sort direction.

The approach

Java

void sort(Deque<Integer> stack) {
    if (stack.isEmpty()) return;
    int top = stack.pop();
    sort(stack);                        // sort everything below first
    insertSorted(stack, top);           // then put this value in its place
}

void insertSorted(Deque<Integer> stack, int val) {
    if (stack.isEmpty() || stack.peek() <= val) {   // largest ends up on top
        stack.push(val);
        return;
    }
    int top = stack.pop();
    insertSorted(stack, val);           // dig below the larger value
    stack.push(top);                    // then restore it on top
}

Python

def sort(stack: list[int]) -> None:               # a list: append / pop / [-1], top at the end
    if not stack:
        return
    top = stack.pop()
    sort(stack)                                   # sort everything below first
    insert_sorted(stack, top)                     # then put this value in its place


def insert_sorted(stack: list[int], val: int) -> None:
    if not stack or stack[-1] <= val:             # largest ends up on top
        stack.append(val)
        return
    top = stack.pop()
    insert_sorted(stack, val)                     # dig below the larger value
    stack.append(top)                             # then restore it on top

Both functions share one shape: pop, recurse on the smaller problem, and push back on the way out. insertSorted must restore what it popped - forgetting stack.push(top) silently drops elements. Each insert may dig through the whole stack, so sorting is O(n^2) time with O(n) recursion depth. Reversing a stack works the same way, with "insert at the bottom" in place of "insert in order".

A second flavor: nested structure

Decoding "2[x3[yz]]" into "xyzyzyzxyzyzyz" needs the same save-and-restore: when a [ opens, remember the text built so far and the repeat count; when ] closes, repeat the inner text and append it to the saved outer text.

Java

String decodeString(String s) {
    Deque<Integer> counts = new ArrayDeque<>();
    Deque<StringBuilder> outer = new ArrayDeque<>();
    StringBuilder cur = new StringBuilder();
    int k = 0;
    for (char c : s.toCharArray()) {
        if (Character.isDigit(c)) {
            k = k * 10 + (c - '0');             // counts can have several digits: "12[ab]"
        } else if (c == '[') {
            counts.push(k);
            outer.push(cur);
            cur = new StringBuilder();
            k = 0;
        } else if (c == ']') {
            String inner = cur.toString();
            cur = outer.pop();
            cur.append(inner.repeat(counts.pop()));
        } else {
            cur.append(c);
        }
    }
    return cur.toString();
}

Python

def decode_string(s: str) -> str:
    counts, outer = [], []
    cur = []                                      # pieces of the current text
    k = 0
    for c in s:
        if c.isdigit():
            k = k * 10 + int(c)                   # counts can have several digits: "12[ab]"
        elif c == "[":
            counts.append(k)
            outer.append(cur)
            cur = []
            k = 0
        elif c == "]":
            inner = "".join(cur)
            cur = outer.pop()
            cur.append(inner * counts.pop())      # str * int repeats the string
        else:
            cur.append(c)
    return "".join(cur)

The explicit stacks here do what recursion would do (a recursive decoder calls itself at each [ and returns at ]). Reading only one digit at a time turns "10[a]" into a single 'a' repeated 0 times after a '1' - the count must accumulate.

Where you'll see it

Parsers and template engines handle nested constructs (JSON inside JSON, {{#each}} blocks inside blocks) with exactly this save-state-on-open, restore-on-close pattern - either as recursion or an explicit stack. Undoing nested transactions and unwinding nested scopes in interpreters work the same way.

Which tool for which job?

You needUse
Sort or reverse a stack, stack operations onlypop, recurse, push back
The same, with a second stack allowedan iterative insertion sort between the two
Nested repeats like "2[x3[yz]]"stacks of saved counts and outer text
The decoded length, or one character of itcount lengths without building the string
Input that could nest very deeplyan explicit stack instead of recursion

Common mistakes

MistakeWhat happensDo this instead
Not pushing back after the recursive callvalues silently disappearevery pop in the recursive case gets a push
Single-digit counts"10[a]" decodes wronglyk = k * 10 + digit, reset at each [
Deep recursionStackOverflowError, or RecursionError at Python's limit of 1,000an explicit stack
Decoding untrusted input blindly19 characters can expand to a billioncompute the length first and cap it
Mixing up which end holds the largestthe stack sorts backwardsdecide the direction and compare accordingly

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Where are the popped values stored while the recursive sort runs?

Show answer

In the local variables of pending calls - one top per frame on the call stack. They are pushed back as those calls return.

2. What goes wrong if insertSorted forgets stack.push(top) after its recursive call?

Show answer

Every value it dug past to reach the insertion point is lost - the stack ends up shorter than it started.

3. In Decode String, why must the repeat count accumulate digits?

Show answer

Counts can have several digits ("12[ab]"). Reading only the last digit makes "10[a]" repeat 0 times; k = k * 10 + digit builds the whole number, and k resets to 0 at each [.

Key takeaways

  • Pop, recurse, push back: the call stack stores the popped values.
  • Recursive stack sort is O(n^2) time and O(n) depth.
  • Nested structure: save the outer state at [, restore and combine at ].
  • Accumulate multi-digit counts; watch the recursion depth.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Sort a stack, stack ops onlypop, recurse, insert
With a second stackiterative insertion sort
Nested repeatsstacks of counts and outer text
Length or k-th charcount lengths, don't build
Deep inputexplicit stack

Snippets worth knowing by heart

Java

void insertSorted(Deque<Integer> st, int v) {
    if (st.isEmpty() || st.peek() <= v) { st.push(v); return; }
    int top = st.pop();
    insertSorted(st, v);
    st.push(top);                                  // always restore
}

Python

def decoded_length(s: str) -> int:                 # never builds the string
    counts, lens, cur, k = [], [], 0, 0
    for c in s:
        if c.isdigit():
            k = k * 10 + int(c)
        elif c == "[":
            counts.append(k); lens.append(cur); cur = 0; k = 0
        elif c == "]":
            cur = lens.pop() + cur * counts.pop()
        else:
            cur += 1
    return cur

The traps

  • No push-back after recursing: values silently disappear. Every pop in the recursive case needs a push.
  • Single-digit counts: "10[a]" decodes wrongly. Build k = k * 10 + digit, and reset at each [.
  • Recursion depth: big stacks overflow, and Python stops at 1,000 frames. Use an explicit stack.
  • Expansion bombs: a short encoded string can expand to a billion characters. Compute the length first and cap it.

Staff-level points, one line each

  • 1,000 values: 518,820 operations random, 2,000 already sorted, 1,001,000 reversed; depth 1,001; RecursionError at Python's default limit.
  • With a second stack, 20,000 values sorted iteratively with no stack overflow.
  • "1000[1000[1000[a]]]": 19 characters decode to a billion; compute the length first.
  • The k-th character comes from lengths and modulo without decoding; it matched on 300 encodings.

Say it in an interview

  • "The call stack holds the popped values; each call pushes its value back on the way out."
  • "It's insertion sort: O(n^2), O(n) when already sorted."
  • "If the input can be deep, I'd use an explicit stack."
  • "For untrusted encodings I'd compute the decoded size before decoding."

Practice

This skill comes with 9 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Recursive Stack Manipulation free

Decode String

MediumProCoding problemJava · Python

Expand an encoded string. k[text] stands for text written k times in a row, and these can sit inside one another.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Sort a Stack

HardProCoding problemJava · Python

Given a stack of integers (as an array, index 0 is the top), sort it so that popping repeatedly yields descending order (the largest value ends up on top).

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Recursive Stack Manipulation free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime