Stack: Practice Problems with Solutions
Welcome to the practice problems for the stack pattern. If you need a refresher on the code, the code templates have the patterns in all 6 languages. Each problem below includes a hint, a visual walkthrough, and the full solution.
Recommended Study Order
The problems are ordered by difficulty, and the progression matters as much as the individual solutions.
- Valid Parentheses teaches the push-and-match loop in its purest form. Master this before adding any other machinery.
- Backspace String Compare applies LIFO filtering to typing, which is the same loop with a simpler match rule.
- Evaluate Reverse Polish Notation shows operators consuming the most recent operands, the workhorse expression pattern.
- Min Stack layers a second stack that tracks a running property alongside the data.
- Daily Temperatures introduces the monotonic stack, where the stack holds candidates waiting for a better value.
- Largest Rectangle in Histogram uses nearest-smaller boundaries on both sides. It is the first genuinely hard stack problem.
- Trapping Rain Water closes pits layer by layer with the same monotonic idea. It is the capstone of the set.
Daily Temperatures and the two hard problems are monotonic stack problems, and the monotonic stack pattern covers that family in depth.
Easy Problems
1. Valid Parentheses
LeetCode 20 | Difficulty: Easy
Brief: Determine whether a string of (, ), {, }, [, and ] brackets is properly nested and closed.
Why this pattern: A closing bracket must match the most recent unclosed opener. That is the LIFO top, and no other structure remembers it.
Hint: What should happen when a closing bracket arrives before any opener was pushed? What should the stack look like when the string is valid?
Visual:
graph LR
A["Input: ( [ ] )"] --> B["'(' goes on the stack"]
B --> C["'[' goes on the stack"]
C --> D["']' matches top '[' and pops it"]
D --> E["')' matches top '(' and pops it"]
E --> F["Stack empty, valid"]
Code:
var isValid = function(s) {
const stack = [];
const matches = { ')': '(', '}': '{', ']': '[' };
for (const c of s) {
if (matches[c]) {
// A closer must match the most recent unclosed
// opener, which is the stack top.
if (stack.pop() !== matches[c]) return false;
} else {
stack.push(c);
}
}
// Openers left on the stack were never closed.
return stack.length === 0;
};The stack holds only openers, so the top is always the most recent unclosed bracket. Two edge cases drive the design. A string that starts with a closer pops from an empty stack, which the pop guard catches. A string that ends with an opener leaves the stack non-empty, which the final length check catches. Both checks together cover every invalid shape.
2. Backspace String Compare
LeetCode 844 | Difficulty: Easy
Brief: Two strings typed into empty text editors use # as a backspace. Determine whether the final texts are equal.
Why this pattern: A backspace removes the most recently typed character. That is LIFO applied to typing.
Hint: Push every non-# character and pop on #. What should happen when # arrives on an empty stack?
Visual:
graph LR
A["Input: a b # c"] --> B["'a' goes on the stack"]
B --> C["'b' goes on the stack"]
C --> D["'#' pops 'b'"]
D --> E["'c' goes on the stack"]
E --> F["Result: a c"]
Code:
var backspaceCompare = function(s, t) {
const build = (str) => {
const stack = [];
for (const char of str) {
if (char === '#') {
// A backspace removes the most recent
// character, which is the stack top.
stack.pop();
} else {
stack.push(char);
}
}
return stack.join('');
};
return build(s) === build(t);
};The build helper applies the same LIFO rule to each string independently, then a plain equality check compares the results. Ruby’s pop on an empty array returns nil and leaves the array unchanged, which is exactly the no-op a backspace on an empty editor should be. The other languages guard the pop explicitly. This problem is a good template to reuse for any “simulate the typing” variant.
Medium Problems
3. Evaluate Reverse Polish Notation
LeetCode 150 | Difficulty: Medium
Brief: Evaluate an arithmetic expression in reverse Polish notation, where each operator follows its two operands.
Why this pattern: An operator applies to the two most recent numbers. A stack of operands is the natural machine for that rule.
Hint: When you pop two operands for subtraction or division, which one is the left operand and which is the right? And what does integer division need to do to negative results?
Visual:
graph LR
A["Tokens: 2 1 + 3 *"] --> B["'2' pushed"]
B --> C["'1' pushed"]
C --> D["'+' pops 1 and 2, pushes 3"]
D --> E["'3' pushed"]
E --> F["'*' pops 3 and 3, pushes 9"]
F --> G["Result: 9"]
Code:
var evalRPN = function(tokens) {
const stack = [];
const ops = {
'+': (a, b) => a + b,
'-': (a, b) => a - b,
'*': (a, b) => a * b,
// Integer division must truncate toward zero,
// so Math.trunc beats Math.floor here.
'/': (a, b) => Math.trunc(a / b)
};
for (const token of tokens) {
if (ops[token]) {
// The first pop is the right operand because
// it sits above the left one on the stack.
const b = stack.pop();
const a = stack.pop();
stack.push(ops[token](a, b));
} else {
stack.push(Number(token));
}
}
return stack[0];
};Two details separate a working RPN evaluator from one that fails hidden tests. Operand order: the first pop is the right operand, so subtraction and division use l - r and l / r, not the reverse. Division rounding: the problem requires truncation toward zero. Java, C++, and Go integer division already truncate toward zero, JavaScript needs Math.trunc, and Ruby needs fdiv(...).to_i because its integer division floors instead.
4. Daily Temperatures
LeetCode 739 | Difficulty: Medium
Brief: For each day, return the number of days until a warmer temperature appears, or 0 if none does.
Why this pattern: Each day needs the next greater value to its right. That is the monotonic stack query, and the stack stores indices so the distance is a subtraction.
Hint: Keep a stack of days still waiting for warmth. When today is warmer than the top of the stack, the answer for that day is the index difference.
Visual:
graph TD
A["Temps: 73 74 75 71 69 72 76"] --> B["73, 74, 75 pushed onto a decreasing stack"]
B --> C["71 is cooler, pushed"]
C --> D["69 is cooler, pushed"]
D --> E["Read 72: warmer than 69 and 71"]
E --> F["answer[4] = 1, answer[3] = 2"]
F --> G["Read 76: warmer than 72 and 75"]
G --> H["answer[5] = 1, answer[2] = 4"]
Code:
var dailyTemperatures = function(temperatures) {
const res = new Array(temperatures.length).fill(0);
const stack = []; // indices of days still waiting
for (let i = 0; i < temperatures.length; i++) {
// Today is warmer than the waiting days on top.
// Their next warmer day is today.
while (stack.length > 0 && temperatures[i] > temperatures[stack[stack.length - 1]]) {
const idx = stack.pop();
res[idx] = i - idx;
}
stack.push(i);
}
return res;
};The stack holds indices, not values, which is the one design decision that makes the answer computable. Each pop writes a distance, and each index is pushed once and popped at most once, so the total work is a single pass. This is a monotonic stack problem, and the monotonic stack pattern covers the family in depth.
5. Min Stack
LeetCode 155 | Difficulty: Medium
Brief: Design a stack whose getMin returns the minimum element in
Why this pattern: A second stack that records every new minimum keeps the current minimum on its own top, so the minimum is never a scan away.
Hint: When should the min stack record a value on push, and when should it release a value on pop?
Visual:
graph LR
A["push(-2)"] --> B["stack [-2], min [-2]"]
B --> C["push(0)"]
C --> D["stack [-2, 0], min [-2]"]
D --> E["push(-3)"]
E --> F["stack [-2, 0, -3], min [-2, -3]"]
F --> G["getMin() returns -3"]
G --> H["pop() removes -3"]
H --> I["min stack top is -2 again"]
Code:
class MinStack {
constructor() {
this.stack = [];
this.minStack = [];
}
push(val) {
this.stack.push(val);
// minStack only records new minimums, so its top is
// always the minimum of the whole stack.
if (this.minStack.length === 0 || val <= this.minStack[this.minStack.length - 1]) {
this.minStack.push(val);
}
}
pop() {
const val = this.stack.pop();
// If the popped value was the current minimum, the
// next entry down in minStack is the new minimum.
if (val === this.minStack[this.minStack.length - 1]) {
this.minStack.pop();
}
}
top() {
return this.stack[this.stack.length - 1];
}
getMin() {
return this.minStack[this.minStack.length - 1];
}
}The <= comparison on push matters. Equal values must be recorded too, because popping one of a pair of equal minimums would otherwise leave a stale minimum on top of the min stack. Trace the sequence push 2, push 2, pop to see why. Each operation stays
Hard Problems
6. Largest Rectangle in Histogram
LeetCode 84 | Difficulty: Hard
Brief: Given an array of bar heights, find the area of the largest rectangle that fits under the histogram.
Why this pattern: The rectangle anchored by a bar is bounded by the nearest shorter bar on each side. Nearest-smaller boundaries are exactly the monotonic stack query.
Hint: When a new bar is shorter than the stack top, the popped bar’s right boundary is the new bar, and its left boundary is whatever is below it on the stack.
Visual:
graph TD
A["Heights: 2, 1, 5, 6, 2, 3"] --> B["Bar 2 arrives at index 4"]
B --> C["Pop bar 6: width 1, area 6"]
C --> D["Pop bar 5: width 2, area 10"]
D --> E["Push bar 2. Its boundaries are bar 1 and the end"]
E --> F["Final pops: bar 2 spans width 4, bar 1 spans width 6"]
F --> G["Maximum area: 10"]
Code:
var largestRectangleArea = function(heights) {
let maxArea = 0;
const stack = [-1]; // anchor index below the first bar
for (let i = 0; i < heights.length; i++) {
// A shorter bar closes the rectangle of every taller
// bar above it. The width runs from the new bar to
// the next bar still on the stack.
while (stack[stack.length - 1] !== -1 && heights[i] <= heights[stack[stack.length - 1]]) {
const h = heights[stack.pop()];
const w = i - stack[stack.length - 1] - 1;
maxArea = Math.max(maxArea, h * w);
}
stack.push(i);
}
// Bars still on the stack run to the end of the array.
while (stack[stack.length - 1] !== -1) {
const h = heights[stack.pop()];
const w = heights.length - stack[stack.length - 1] - 1;
maxArea = Math.max(maxArea, h * w);
}
return maxArea;
};The sentinel -1 on the stack is the trick that removes every edge case. It acts as a virtual bar of height zero before the array, so the width calculation works uniformly and the final drain loop needs no separate guard for an empty stack. The stack stays increasing in height, and each index is pushed and popped once, so the whole algorithm is
7. Trapping Rain Water
LeetCode 42 | Difficulty: Hard
Brief: Given an array of elevations, compute how much water is trapped after a rain.
Why this pattern: Water collects above any bar whose neighbors on both sides are taller. A decreasing stack finds each pit as a taller bar arrives and closes it.
Hint: Each pop with a taller current bar and a taller bar still on the stack forms one horizontal layer of water. Multiply the layer height by the distance between the two walls.
Visual:
graph TD
A["Heights: 0, 1, 0, 2"] --> B["Push 0 and 1"]
B --> C["Push 0 at index 2"]
C --> D["Read 2: taller than the bar at index 2"]
D --> E["Pop the 0. Left wall is bar 1, right wall is bar 2"]
E --> F["Layer: min(1, 2) - 0 = 1, width 1"]
F --> G["Total water so far: 1"]
Code:
var trap = function(height) {
let water = 0;
const stack = [];
for (let i = 0; i < height.length; i++) {
// The current bar closes pits above the stack top.
// Each pop is one horizontal layer of water.
while (stack.length > 0 && height[i] > height[stack[stack.length - 1]]) {
const top = stack.pop();
// No left wall means no pit, so nothing to trap.
if (stack.length === 0) break;
const distance = i - stack[stack.length - 1] - 1;
const boundedHeight = Math.min(height[i], height[stack[stack.length - 1]]) - height[top];
water += distance * boundedHeight;
}
stack.push(i);
}
return water;
};The stack holds indices of bars that still have a taller bar waiting on the left. When a taller bar arrives, each bar popped off the stack forms one layer of water between its left wall and the new right wall. The layer height is the difference between the two wall heights and the popped bar, and the width is the distance between the walls minus one. If no left wall remains after a pop, the popped bar was a slope, not a pit, and nothing is trapped. Every index is pushed and popped once, so the running time is
These seven problems cover the stack pattern end to end. Valid Parentheses and Backspace String Compare build the push-and-match loop. Evaluate Reverse Polish Notation and Min Stack add the two workhorse variants. Daily Temperatures opens the monotonic family, and Largest Rectangle in Histogram and Trapping Rain Water cap it off. By the end, you should be able to spot the LIFO shape in a problem and reach for the right template without hesitating.