A Min/Max Stack is a normal stack that can return the minimum or maximum value in O(1).
Its core advantage:
The top of an auxiliary stack always remembers the answer as of that level — so popping restores the previous min/max automatically.
Focus on recognizing:
“getMin()/getMax()” + O(1) = Track Running State
Pattern Table
| Pattern | Typical Questions | Trigger |
|---|---|---|
| Two Stacks | Design MinStack | Aux stack stores running min |
| Value + Pair | MinStack with one stack | Each entry carries its own state |
| Max Variant | Design MaxStack | Flip min to max |
Mental Trigger
Push → Update state | Pop → Remove state | Query → Peek state.
1. Generic State-Tracking Template (Base)
Watch pushes build [5, 3, 7, 2] while the aux stack snapshots the minimum at each level, then watch pop() restore the previous minimum. Press ▶ to animate, or step through manually.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
Min Stack (Two Stacks)
Design a stack that supports push, pop, top, and getMin in O(1). Mirror the main stack with an aux stack that stores the running minimum at each depth, so getMin just peeks the aux top.
Push 5, 3, 7, 2, 3, 7, 2. The MAIN stack is drawn; the AUX stack (running min) is shown in the state chips. Watch both grow together on push and shrink together on pop — popping the top automatically restores the previous minimum. The top pointer marks the current top of both stacks.
1
push(v): main.push(v)
2
minStack.push(min(v, minStack.top()))
3
pop(): main.pop(); minStack.pop()
4
getMin(): return minStack.top()
public void push(int val) {
main.push(val);
// aux.push( combine(val, aux.peek()) );
}
public void pop() {
main.pop();
// aux.pop(); — keep BOTH stacks in sync
}
public int query() {
return aux.peek(); // O(1), no scanning
}def push(self, val: int) -> None:
self.main.append(val)
# self.aux.append(combine(val, self.aux[-1]))
def pop(self) -> None:
self.main.pop()
# self.aux.pop() — keep BOTH stacks in sync
def query(self) -> int:
return self.aux[-1] # O(1), no scanningvoid push(int val) {
main_.push(val);
// aux_.push(combine(val, aux_.top()));
}
void pop() {
main_.pop();
// aux_.pop(); — keep BOTH stacks in sync
}
int query() {
return aux_.top(); // O(1), no scanning
}push(val) {
this.main.push(val);
// this.aux.push(combine(val, this.aux.at(-1)));
}
pop() {
this.main.pop();
// this.aux.pop(); — keep BOTH stacks in sync
}
query() {
return this.aux[this.aux.length - 1]; // O(1)
}Everything else in Min/Max Stack is just a modification of this template.
Two rules baked into the base:
- Every push updates the state — no exceptions.
- Every pop removes the state — the stacks stay index-aligned.
Pattern 1: Min Stack — Two Stacks
Main holds values; aux snapshots the running min at every depth.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
Min Stack (Two Stacks)
Design a stack that supports push, pop, top, and getMin in O(1). Use an auxiliary stack that mirrors the main stack but stores the running minimum at each depth.
Push 5, 3, 7, 2. The aux stack records the minimum at each level. Watch both stacks grow together, then shrink on pop.
1
push(x): main.push(x); aux.push(min(x, aux.top))
2
pop(): main.pop(); aux.pop()
3
getMin(): return aux.top
Code
class MinStack {
Stack<Integer> main = new Stack<>();
Stack<Integer> min = new Stack<>();
public void push(int val) {
main.push(val);
min.push(min.isEmpty()
? val
: Math.min(val, min.peek()));
}
public void pop() {
main.pop();
min.pop();
}
public int top() { return main.peek(); }
public int getMin() { return min.peek(); }
}class MinStack:
def __init__(self):
self.main = []
self.min = []
def push(self, val: int) -> None:
self.main.append(val)
self.min.append(min(val, self.min[-1])
if self.min else val)
def pop(self) -> None:
self.main.pop()
self.min.pop()
def top(self) -> int:
return self.main[-1]
def getMin(self) -> int:
return self.min[-1]class MinStack {
stack<int> main_, min_;
public:
void push(int val) {
main_.push(val);
min_.push(min_.empty()
? val
: ::min(val, min_.top()));
}
void pop() { main_.pop(); min_.pop(); }
int top() { return main_.top(); }
int getMin() { return min_.top(); }
};class MinStack {
constructor() {
this.main = [];
this.min = [];
}
push(val) {
this.main.push(val);
this.min.push(
this.min.length === 0
? val
: Math.min(val, this.min[this.min.length - 1]),
);
}
pop() {
this.main.pop();
this.min.pop();
}
top() { return this.main[this.main.length - 1]; }
getMin() { return this.min[this.min.length - 1]; }
}What Changed from the Base Template?
Fill in combine as min
Base:
aux.push(combine(val, aux.peek()));self.aux.append(combine(val, self.aux[-1]))aux_.push(combine(val, aux_.top()));this.aux.push(combine(val, this.aux.at(-1)));Changed:
min.push(min.isEmpty()
? val
: Math.min(val, min.peek()));self.min.append(min(val, self.min[-1])
if self.min else val)min_.push(min_.empty()
? val
: ::min(val, min_.top()));this.min.push(
this.min.length === 0
? val
: Math.min(val, this.min[this.min.length - 1]),
);because the empty-aux case has no previous minimum to compare against.
Min Stack = Base Template +
combine = min+ sync both stacks on pop.
Pattern 2: Min Stack — Single Stack (Value + Pair)
Each entry carries its own min — history stored inside the pairs.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
Min Stack (Single Stack with Pairs)
Instead of two stacks, store [value, current_min] pairs in a single stack. Each entry remembers the minimum at its depth — pop restores the previous min automatically.
Push 5|5, 3|3, 7|3, 2|2. Each pair carries the running min. Pop discards the top pair and the min falls back.
1
push(x): m = min(x, top.min); push [x, m]
2
pop(): pop [_, m]; return it
3
getMin(): return top[1]
Same behavior, half the bookkeeping: each entry carries {value, minSoFar}.
Code
class MinStackSingle {
record Entry(int val, int minSoFar) {}
Deque<Entry> stack = new ArrayDeque<>();
public void push(int val) {
int min = stack.isEmpty()
? val
: Math.min(val, stack.peek().minSoFar());
stack.push(new Entry(val, min));
}
public void pop() { stack.pop(); }
public int top() { return stack.peek().val(); }
public int getMin() { return stack.peek().minSoFar(); }
}class MinStackSingle:
def __init__(self):
self.stack = [] # entries: (val, min_so_far)
def push(self, val: int) -> None:
m = val if not self.stack \
else min(val, self.stack[-1][1])
self.stack.append((val, m))
def pop(self) -> None:
self.stack.pop()
def top(self) -> int:
return self.stack[-1][0]
def getMin(self) -> int:
return self.stack[-1][1]class MinStackSingle {
struct Entry { int val; int minSoFar; };
stack<Entry> st;
public:
void push(int val) {
int m = st.empty()
? val
: min(val, st.top().minSoFar);
st.push({val, m});
}
void pop() { st.pop(); }
int top() { return st.top().val; }
int getMin() { return st.top().minSoFar; }
};class MinStackSingle {
constructor() {
this.stack = []; // entries: [val, minSoFar]
}
push(val) {
const m = this.stack.length === 0
? val
: Math.min(val, this.stack[this.stack.length - 1][1]);
this.stack.push([val, m]);
}
pop() { this.stack.pop(); }
top() { return this.stack[this.stack.length - 1][0]; }
getMin() { return this.stack[this.stack.length - 1][1]; }
}What Changed from the Base Template?
One stack instead of two
Base:
Stack<Integer> main;
Stack<Integer> aux; // separate state stackself.main = []
self.aux = [] # separate state stackstack<int> main_;
stack<int> aux_; // separate state stackthis.main = [];
this.aux = []; // separate state stackChanged:
Deque<Entry> stack; // value + state travel togetherself.stack = [] # (val, min_so_far) tuplesstack<Entry> st; // value + state travel togetherthis.stack = []; // [val, minSoFar] pairsbecause bundling state with each value makes desync impossible by construction.
Single-Stack Min = Base Template + Store
{value, state}per entry.
Pattern 3: Max Stack
One flipped comparison turns the whole pattern into a max tracker.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
Max Stack
Same as Min Stack but tracks the maximum. The aux stack stores the running max at each depth — pop restores the previous max in O(1).
Push 3, 5, 1, 6. Aux mirrors the main stack but stores max values. Watch the max change as elements are pushed and popped.
1
push(x): aux.push(max(x, aux.top))
2
pop(): main.pop(); aux.pop()
3
getMax(): return aux.top
Flip one token — combine = max.
Code
public void push(int val) {
max.push(max.isEmpty()
? val
: Math.max(val, max.peek()));
}def push(self, val: int) -> None:
self.max.append(max(val, self.max[-1])
if self.max else val)void push(int val) {
max_.push(max_.empty()
? val
: ::max(val, max_.top()));
}push(val) {
this.max.push(
this.max.length === 0
? val
: Math.max(val, this.max[this.max.length - 1]),
);
}Everything else — pop sync, peek query — is identical to Min Stack.
Min →
combine = min| Max →combine = max. Nothing else changes.
Complexity
| Operation | Time |
|---|---|
push() | O(1) |
pop() | O(1) |
top() | O(1) |
getMin() | O(1) |
getMax() | O(1) |
| Space | O(n) |
Min/Max Stack Pattern Evolution
Base State-Tracking Stack
↓
Min Stack — Two Stacks
(+ combine = min + empty-guard)
↓
Min Stack — Single Stack
(+ bundle {value, state} per entry)
↓
Max Stack
(+ flip combine to max)
Common Mistakes
Not updating state on push.
// Wrong — min goes stale
main.push(val);
// Correct
main.push(val);
min.push(Math.min(val, min.peek()));# Wrong
self.main.append(val)
# Correct
self.main.append(val)
self.min.append(min(val, self.min[-1]))// Wrong
main_.push(val);
// Correct
main_.push(val);
min_.push(::min(val, min_.top()));// Wrong
this.main.push(val);
// Correct
this.main.push(val);
this.min.push(Math.min(val, this.min.at(-1)));Desyncing the stacks on pop.
Both stacks must pop together — or use the pair layout so desync is impossible.
Querying by scan.
Scanning for the minimum is O(n) and defeats the entire design. The whole point is:
getMin() → aux.peek() O(1)
Empty-guard on first push.
The first comparison needs a previous state that doesn’t exist yet — guard with isEmpty() before combining.
Recognition Cheat Sheet
| If you see… | Think… |
|---|---|
getMin() in O(1) | Aux stack of running minimums |
getMax() in O(1) | Same, flipped comparator |
| Pop must restore previous min/max | Snapshot state at every level |
| Constant memory requested | Value-encoded trick (advanced) |
Premium Content
Unlock Min & Max Stack and all premium lessons with a subscription.
From ₹199.99/year — See plans