A monotonic stack keeps elements in increasing or decreasing order.
When a new element breaks the order, pop until the order is restored.
Its core advantage:
Each element is pushed and popped at most once → O(n) instead of O(n²).
Focus on recognizing:
“Next/previous” + “greater/smaller” = Monotonic Stack
Pattern Table
| Pattern | Typical Questions | Trigger |
|---|---|---|
| Next Greater | Next Greater Element, Daily Temps | Pop smaller, assign on pop |
| Stock Span | Span of today’s price | Keep useful previous indices |
| Largest Rectangle | Largest Rectangle in Histogram | Pop taller bars, compute width |
Mental Trigger
Pop while order is violated → Process popped → Push current.
1. Generic Monotonic Stack Template (Base)
Watch the decreasing stack resolve [4, 5, 2, 25] — index 1 (value 25) pops two waiting indices at once. Press ▶ to animate, or step through manually with the arrows and speed control.
⚠️ 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.
Next Greater Element (Monotonic Decreasing Stack)
For each element, find the first element to its right that is larger. A decreasing stack of indices holds elements still awaiting their answer; when a bigger element arrives it resolves (pops) every smaller one.
Array [4,5,2,25]. The stack (drawn with VALUES) holds indices whose next-greater is still unknown, kept in strictly DECREASING value order. When nums[i] is bigger than the top, that top gets nums[i] as its answer and is popped. Leftovers end with -1. The state chips show the running result; each index is pushed and popped once → O(n).
1
result = [-1, -1, ..., -1]
2
for i from 0 to n-1:
3
while stack not empty AND nums[stack.top] < nums[i]:
4
j = pop stack; result[j] = nums[i]
5
push i onto stack
6
return result
public void monotonicStack(int[] nums) {
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < nums.length; i++) {
while (!stack.isEmpty() && /* order violated */) {
int j = stack.pop();
// process j — nums[i] answers j
}
stack.push(i);
}
}def monotonic_stack(nums: list[int]) -> None:
stack = []
for i, num in enumerate(nums):
while stack and /* order violated */:
j = stack.pop()
# process j — num answers j
stack.append(i)void monotonicStack(vector<int>& nums) {
stack<int> st;
for (int i = 0; i < nums.size(); i++) {
while (!st.empty() && /* order violated */) {
int j = st.top(); st.pop();
// process j — nums[i] answers j
}
st.push(i);
}
}function monotonicStack(nums) {
const stack = [];
for (let i = 0; i < nums.length; i++) {
while (stack.length && /* order violated */) {
const j = stack.pop();
// process j — nums[i] answers j
}
stack.push(i);
}
}Everything else in Monotonic Stack is just a modification of this template.
Two rules to fill the placeholder:
- Decreasing stack (pop smaller) → finds next greater
- Increasing stack (pop larger) → finds next smaller
Pattern 1: Next Greater Element
A decreasing stack of values; each arrival resolves everyone smaller.
⚠️ 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.
Next Greater Element
For each element, find the next greater element to its right. A decreasing stack holds elements waiting for their answer. When a bigger element arrives, it resolves all smaller ones.
Array: [4,5,2,25]. Decreasing stack of values. Each new element pops everything smaller — those popped elements get the new element as their answer.
1
stack = [] // values waiting for their NGE
2
for x in nums:
3
while stack and x > stack.top:
4
answer[pop()] = x
5
push(x)
6
leftovers → answer = -1
Code
public int[] nextGreaterElement(int[] nums) {
int[] result = new int[nums.length];
Arrays.fill(result, -1);
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < nums.length; i++) {
while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
result[stack.pop()] = nums[i];
}
stack.push(i);
}
return result;
}def next_greater_element(nums: list[int]) -> list[int]:
result = [-1] * len(nums)
stack = []
for i, num in enumerate(nums):
while stack and nums[stack[-1]] < num:
result[stack.pop()] = num
stack.append(i)
return resultvector<int> nextGreaterElement(vector<int>& nums) {
vector<int> result(nums.size(), -1);
stack<int> st;
for (int i = 0; i < nums.size(); i++) {
while (!st.empty() && nums[st.top()] < nums[i]) {
result[st.top()] = nums[i];
st.pop();
}
st.push(i);
}
return result;
}function nextGreaterElement(nums) {
const result = Array(nums.length).fill(-1);
const stack = [];
for (let i = 0; i < nums.length; i++) {
while (stack.length && nums[stack[stack.length - 1]] < nums[i]) {
result[stack.pop()] = nums[i];
}
stack.push(i);
}
return result;
}What Changed from the Base Template?
Fill the condition and the processing step
Base:
while (!stack.isEmpty() && /* order violated */) {
// process j
}while stack and /* order violated */:
pass # process jwhile (!st.empty() && /* order violated */) {
// process j
}while (stack.length && /* order violated */) {
// process j
}Changed:
while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
result[stack.pop()] = nums[i];
}while stack and nums[stack[-1]] < num:
result[stack.pop()] = numwhile (!st.empty() && nums[st.top()] < nums[i]) {
result[st.top()] = nums[i];
st.pop();
}while (stack.length && nums[stack[stack.length - 1]] < nums[i]) {
result[stack.pop()] = nums[i];
}because a smaller top is waiting for a greater element, and nums[i] is that answer.
Next Greater = Base Template + Decreasing stack + Assign answer on pop.
Pattern 2: Daily Temperatures
Same stack of indices; the answer is a waiting DISTANCE instead of a value.
⚠️ 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.
Daily Temperatures (Monotonic Decreasing Stack)
For each day, find how many days until a warmer temperature. A decreasing stack stores indices waiting for a warmer day; when a hotter day arrives it resolves (pops) every cooler day that was waiting.
Temps [73,74,75,71,72,76]. The stack holds indices whose answer is still unknown, with temperatures strictly DECREASING bottom→top. When nums[i] is hotter than the top, that top finally gets its answer (i - top). Each index is pushed and popped at most once, so it's O(n). The highlighted cell is the index being resolved/added; the array shows the answer filling in.
1
stack = [] // indices, temps decreasing
2
for i in 0..n-1:
3
while stack and nums[i] > nums[stack.top]:
4
j = pop(); answer[j] = i - j
5
push(i)
Same stack — but the answer is how many days until a warmer temperature, not the value itself.
Code
public int[] dailyTemperatures(int[] temps) {
int[] result = new int[temps.length];
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < temps.length; i++) {
while (!stack.isEmpty() && temps[stack.peek()] < temps[i]) {
int j = stack.pop();
result[j] = i - j;
}
stack.push(i);
}
return result;
}def daily_temperatures(temps: list[int]) -> list[int]:
result = [0] * len(temps)
stack = []
for i, t in enumerate(temps):
while stack and temps[stack[-1]] < t:
j = stack.pop()
result[j] = i - j
stack.append(i)
return resultvector<int> dailyTemperatures(vector<int>& temps) {
vector<int> result(temps.size(), 0);
stack<int> st;
for (int i = 0; i < temps.size(); i++) {
while (!st.empty() && temps[st.top()] < temps[i]) {
int j = st.top(); st.pop();
result[j] = i - j;
}
st.push(i);
}
return result;
}function dailyTemperatures(temps) {
const result = Array(temps.length).fill(0);
const stack = [];
for (let i = 0; i < temps.length; i++) {
while (stack.length && temps[stack[stack.length - 1]] < temps[i]) {
const j = stack.pop();
result[j] = i - j;
}
stack.push(i);
}
return result;
}What Changed from the Base Template?
Store distance instead of value
Base:
result[stack.pop()] = nums[i];result[stack.pop()] = numresult[st.top()] = nums[i];result[stack.pop()] = nums[i];Changed:
int j = stack.pop();
result[j] = i - j;j = stack.pop()
result[j] = i - jint j = st.top(); st.pop();
result[j] = i - j;const j = stack.pop();
result[j] = i - j;because we need the gap between the two indices — this only works because the stack stores indices, not values.
Daily Temperatures = Base Template + Answer as index distance.
Pattern 3: Largest Rectangle in Histogram
Pops compute width × height — every bar’s best rectangle ends at a shorter neighbour.
⚠️ 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.
Largest Rectangle in Histogram
Find the biggest rectangle that fits under a histogram. An increasing stack of indices tracks heights; when a shorter bar arrives it pops taller bars, and each popped bar's rectangle spans from the new top to the current index.
Bars [2,1,5,6,2,3] → area 10 (the 5×2 block). The stack holds indices with STRICTLY INCREASING heights. When a shorter bar arrives, popping a bar computes width = (current index) - (new stack top) - 1 and area = height × width. A sentinel keeps the final flush. The highlighted cell is the bar being resolved.
1
stack = [] // indices, heights increasing
2
for i in bars (+ sentinel 0):
3
while h[i] < h[stack.top]:
4
height = h[pop()]
5
width = i - stack.top - 1
6
best = max(best, height*width)
Flip to an increasing stack: when a shorter bar arrives, taller bars can’t extend further right — settle their area immediately.
Code
public int largestRectangleArea(int[] heights) {
Stack<Integer> stack = new Stack<>();
int max = 0;
for (int i = 0; i <= heights.length; i++) {
int h = (i == heights.length) ? 0 : heights[i];
while (!stack.isEmpty() && h < heights[stack.peek()]) {
int height = heights[stack.pop()];
int width = stack.isEmpty()
? i
: i - stack.peek() - 1;
max = Math.max(max, height * width);
}
stack.push(i);
}
return max;
}def largest_rectangle_area(heights: list[int]) -> int:
stack = []
max_area = 0
for i in range(len(heights) + 1):
h = 0 if i == len(heights) else heights[i]
while stack and h < heights[stack[-1]]:
height = heights[stack.pop()]
width = i if not stack else i - stack[-1] - 1
max_area = max(max_area, height * width)
stack.append(i)
return max_areaint largestRectangleArea(vector<int>& heights) {
stack<int> st;
int maxArea = 0;
for (int i = 0; i <= heights.size(); i++) {
int h = (i == heights.size()) ? 0 : heights[i];
while (!st.empty() && h < heights[st.top()]) {
int height = heights[st.top()];
st.pop();
int width = st.empty() ? i : i - st.top() - 1;
maxArea = max(maxArea, height * width);
}
st.push(i);
}
return maxArea;
}function largestRectangleArea(heights) {
const stack = [];
let max = 0;
for (let i = 0; i <= heights.length; i++) {
const h = i === heights.length ? 0 : heights[i];
while (stack.length && h < heights[stack[stack.length - 1]]) {
const height = heights[stack.pop()];
const width = stack.length === 0
? i
: i - stack[stack.length - 1] - 1;
max = Math.max(max, height * width);
}
stack.push(i);
}
return max;
}What Changed from the Base Template?
Increasing stack + width calculation
Base:
while (!stack.isEmpty() && nums[stack.peek()] < nums[i])while stack and nums[stack[-1]] < num:while (!st.empty() && nums[st.top()] < nums[i])while (stack.length && nums[stack[stack.length - 1]] < nums[i])Changed:
while (!stack.isEmpty() && h < heights[stack.peek()])while stack and h < heights[stack[-1]]:while (!st.empty() && h < heights[st.top()])while (stack.length && h < heights[stack[stack.length - 1]])because a shorter bar ends the reach of every taller bar on the stack.
Sentinel flush at the end
Added:
int h = (i == heights.length) ? 0 : heights[i];h = 0 if i == len(heights) else heights[i]int h = (i == heights.size()) ? 0 : heights[i];const h = i === heights.length ? 0 : heights[i];because the final 0 pops every remaining bar so no area is left uncounted.
The stack top after popping is each bar’s previous smaller element, giving the width:
width = i - previousSmallerIndex - 1
Histogram = Base Template + Increasing stack + Height × Width + Sentinel flush.
Monotonic Stack Pattern Evolution
Base Monotonic Stack
↓
Next Greater Element
(+ decreasing stack + assign answer on pop)
↓
Daily Temperatures
(+ answer as index distance)
↓
Largest Rectangle in Histogram
(+ increasing stack + width calc + sentinel flush)
Common Mistakes
Storing values when you need positions.
Any question about distance, span, or width must push indices:
// Wrong — value alone can't recover distance or position
Stack<Integer> values;
// Correct
Stack<Integer> indices;# Wrong
stack.append(num)
# Correct
stack.append(i)// Wrong
stack<int> values;
// Correct
stack<int> indices;// Wrong
stack.push(num);
// Correct
stack.push(i);Wrong comparison direction.
nums[top] < current pops smaller → next greater.
For next smaller, flip it to nums[top] > current.
Pick < or <= deliberately — it decides how duplicates are grouped.
Forgetting the default answer.
Elements never resolved keep their initial fill:
Arrays.fill(result, -1); // or new int[n] for 0 defaultsresult = [-1] * n # or [0] * nvector<int> result(n, -1); // or vector<int> result(n, 0)const result = Array(n).fill(-1); // or .fill(0)Forgetting the histogram sentinel.
Without the final h = 0 pass, bars still on the stack are never settled and their areas are lost.
Recognition Cheat Sheet
| If you see… | Think… |
|---|---|
| Next/previous greater | Decreasing stack |
| Next/previous smaller | Increasing stack |
| Days/span until bigger/smaller | Monotonic stack + distances |
| Largest rectangle / area of bars | Increasing stack + width calc |
Premium Content
Unlock Monotonic Stack and all premium lessons with a subscription.
From ₹199.99/year — See plans