Stage 1: Foundations·Stack & Queue Fundamentals
Medium#1556 min readStackDesignLeetCode

Min Stack

Parallel minimum stack

Auxiliary structure trick: maintain a parallel stack of minimums.

Published Apr 15, 2026

Problem

Design a stack that supports push, pop, top, and getMin in constant time.

getMin must return the minimum element currently in the stack.

Input
operations = ["MinStack","push","push","push","getMin","pop","top","getMin"]
Output
[null,null,null,null,-3,null,0,-2]
Explanation
The minimum is tracked as the stack changes, so getMin never scans.
Input
operations = ["MinStack","push","push","getMin","pop","getMin"]
Output
[null,null,null,1,null,1]
Explanation
Duplicate minima must remain visible after popping one copy.

Intuition

The trick is not to search for the minimum after the fact. That would make getMin linear.

Instead, cache the minimum at each depth of the stack. Every time you push a value, also push the minimum seen so far. Then the current minimum is always the top of the auxiliary stack.

This is the same design move you use in production systems when a derived value is queried often: pay the cost once on mutation, then read it in O(1).

Approach

Parallel Minimum Stack

TimeO(1)
SpaceO(n)
Recommended

Maintain two stacks:

  • values stores the actual pushed values.
  • mins stores the minimum value for the prefix ending at the same depth.

On push(x), append x to values and min(x, current_min) to mins. On pop(), remove one item from both stacks. top() and getMin() are just reads from the back.

pub struct MinStack {
    values: Vec<i32>,
    mins: Vec<i32>,
}

impl MinStack {
    pub fn new() -> Self {
        Self {
            values: Vec::new(),
            mins: Vec::new(),
        }
    }

    pub fn push(&mut self, x: i32) {
        let min_so_far = match self.mins.last().copied() {
            Some(curr) => curr.min(x),
            None => x,
        };
        self.values.push(x);
        self.mins.push(min_so_far);
    }

    pub fn pop(&mut self) {
        self.values.pop();
        self.mins.pop();
    }

    pub fn top(&self) -> i32 {
        *self.values.last().unwrap()
    }

    pub fn get_min(&self) -> i32 {
        *self.mins.last().unwrap()
    }
}

Language notes:

  • Rust — mirror the two stacks exactly; it keeps the invariant obvious and avoids any branchy "recompute minimum" logic.
  • TypeScript — the array-back pattern is the same as Vec::push / Vec::pop; the implementation stays tiny because mins already stores the prefix minimum.

Trace

Push -2, 0, -3, then ask for the minimum:

opvalues stackmins stackresult
push -2[-2][-2]-
push 0[-2, 0][-2, -2]-
push -3[-2, 0, -3][-2, -2, -3]-
getMin[-2, 0, -3][-2, -2, -3]-3
pop[-2, 0][-2, -2]-
top[-2, 0][-2, -2]0
getMin[-2, 0][-2, -2]-2

The key invariant is visible in the second column: the top of mins always matches the minimum of values up to that depth.

Edge cases

  • Duplicate minima — push the same minimum again when the new value ties the current minimum.
  • Negative values — no special handling; the minimum logic is purely comparative.
  • Popping the last element — both stacks shrink together, so the data structure stays aligned.
💡
Tip

This is the simplest version of a broader pattern: store a derived aggregate per prefix. The same idea appears in prefix sums, rolling minima, and persistent snapshots.

Takeaway

If a query depends on all items currently in a stack, ask whether that answer can be cached on the way in. That is the whole trick here.

The implementation is tiny, but the design pattern is important: build the expensive part into the mutation path so the read path stays O(1).

More in Stack & Queue Fundamentals