Monotonic Stack Data Structure

Learn Monotonic Stack Data Structure with practical examples, explanations, best practices, and real-world coding techniques.

What Is a Monotonic Stack?

A monotonic stack is a stack-based technique in which elements are maintained in a specific increasing or decreasing order. It is commonly used to solve problems that ask for the next greater element, next smaller element, previous greater element, or previous smaller element.

The important idea is that elements that can no longer be useful for answering future queries are removed from the stack. This allows many problems that appear to require nested loops to be solved in linear O(n) time.

Core idea

Maintain a stack whose values follow a required order. Before adding a new element, remove stack elements that violate that order.

Why Use a Monotonic Stack?

Consider the problem of finding the next greater element for every value in an array. A straightforward solution could compare every element with all elements to its right. In the worst case, this requires O(n²) time.

A monotonic stack avoids repeatedly comparing elements that have already been resolved or eliminated. Each element is pushed onto the stack once and removed at most once, resulting in an overall O(n) solution.

Types of Monotonic Stacks

The two main forms are based on the order maintained inside the stack.

Type Stack Property Common Use
Monotonic Increasing Values remain increasing from bottom to top Next smaller element patterns
Monotonic Decreasing Values remain decreasing from bottom to top Next greater element patterns

The exact choice depends on the relationship the problem asks you to find. It is often easier to decide which stack to use by focusing on what should happen when the current element is compared with the stack's top element.

Monotonic Decreasing Stack

A monotonic decreasing stack keeps values in decreasing order from the bottom toward the top. This pattern is commonly used when looking for the next greater element.

Suppose the input is:

[2, 1, 2, 4, 3]

When a larger value arrives, elements smaller than the current value can be resolved because the current value is their next greater element.

Next Greater Element

The next greater element for an element is the first element to its right that is greater than it. If no such element exists, the result is usually represented by -1.

For example:

Input:
[2, 1, 2, 4, 3]

Output:
[4, 2, 4, -1, -1]

For the first value, 2, the next greater value is 4. For 1, it is 2. The value 4 has no greater element to its right.

Solving Next Greater Element with a Stack

One useful approach is to traverse the array from right to left. The stack stores candidates that may be the next greater element for values encountered later.

  1. Start from the rightmost element.
  2. Remove stack elements that are smaller than or equal to the current value.
  3. The remaining stack top, if present, is the next greater element.
  4. Push the current value onto the stack.
  5. Continue toward the beginning of the array.
const numbers = [2, 1, 2, 4, 3];
const result = new Array(numbers.length).fill(-1);
const stack = [];

for (let i = numbers.length - 1; i >= 0; i--) {
  while (
    stack.length > 0 &&
    stack[stack.length - 1] <= numbers[i]
  ) {
    stack.pop();
  }

  if (stack.length > 0) {
    result[i] = stack[stack.length - 1];
  }

  stack.push(numbers[i]);
}

console.log(result);
// [4, 2, 4, -1, -1]

How the Stack Changes

Understanding the stack state is often more useful than memorizing the code. For the array [2, 1, 2, 4, 3], the algorithm processes values from right to left.

Current Action Result
3 No greater candidate -1
4 Remove 3 -1
2 4 is greater 4
1 2 is greater 2
2 Remove 1, then find 4 4

Next Smaller Element

The same technique can be reversed to find the first smaller element to the right. In this case, elements that are greater than or equal to the current value can be removed from the stack.

const numbers = [4, 5, 2, 10, 8];

const result = new Array(numbers.length).fill(-1);
const stack = [];

for (let i = numbers.length - 1; i >= 0; i--) {
  while (
    stack.length > 0 &&
    stack[stack.length - 1] >= numbers[i]
  ) {
    stack.pop();
  }

  if (stack.length > 0) {
    result[i] = stack[stack.length - 1];
  }

  stack.push(numbers[i]);
}

console.log(result);
// [2, 2, -1, 8, -1]

Previous Greater Element

Monotonic stacks can also solve problems involving elements to the left of the current position. For a previous greater element, the array can be processed from left to right while maintaining a suitable decreasing stack.

const numbers = [10, 4, 2, 20, 40, 12, 30];

const result = new Array(numbers.length).fill(-1);
const stack = [];

for (let i = 0; i < numbers.length; i++) {
  while (
    stack.length > 0 &&
    stack[stack.length - 1] <= numbers[i]
  ) {
    stack.pop();
  }

  if (stack.length > 0) {
    result[i] = stack[stack.length - 1];
  }

  stack.push(numbers[i]);
}

console.log(result);

Previous Smaller Element

Similarly, a monotonic stack can identify the closest smaller value to the left by removing elements that cannot be valid candidates.

const numbers = [4, 5, 2, 10, 8];

const result = new Array(numbers.length).fill(-1);
const stack = [];

for (let i = 0; i < numbers.length; i++) {
  while (
    stack.length > 0 &&
    stack[stack.length - 1] >= numbers[i]
  ) {
    stack.pop();
  }

  if (stack.length > 0) {
    result[i] = stack[stack.length - 1];
  }

  stack.push(numbers[i]);
}

console.log(result);
// [-1, 4, -1, 2, 2]

The Four Common Patterns

Problem Typical Direction Stack Behavior
Next greater Right to left Remove smaller or equal values
Next smaller Right to left Remove greater or equal values
Previous greater Left to right Remove smaller or equal values
Previous smaller Left to right Remove greater or equal values

Monotonic Stack with Indexes

In many problems, storing values alone is not enough. You may need to know the position where an element occurred. In that case, store indexes in the stack and use those indexes to access the original array.

const numbers = [2, 1, 2, 4, 3];
const result = new Array(numbers.length).fill(-1);
const stack = [];

for (let i = numbers.length - 1; i >= 0; i--) {
  while (
    stack.length > 0 &&
    numbers[stack[stack.length - 1]] <= numbers[i]
  ) {
    stack.pop();
  }

  if (stack.length > 0) {
    result[i] = numbers[stack[stack.length - 1]];
  }

  stack.push(i);
}

console.log(result);
// [4, 2, 4, -1, -1]

Index-based stacks are especially useful when the answer requires a distance, position, or range rather than only the neighboring value.

Daily Temperatures Pattern

A classic monotonic stack problem asks how many days a person must wait before encountering a warmer temperature.

For example:

Temperatures:
[73, 74, 75, 71, 69, 72, 76, 73]

Answer:
[1, 1, 4, 2, 1, 1, 0, 0]

The stack stores indexes of temperatures that are still waiting for a warmer day. When a warmer temperature is encountered, the waiting indexes can be resolved.

const temperatures = [73, 74, 75, 71, 69, 72, 76, 73];

const answer = new Array(temperatures.length).fill(0);
const stack = [];

for (let i = 0; i < temperatures.length; i++) {
  while (
    stack.length > 0 &&
    temperatures[i] > temperatures[stack[stack.length - 1]]
  ) {
    const previousIndex = stack.pop();
    answer[previousIndex] = i - previousIndex;
  }

  stack.push(i);
}

console.log(answer);
// [1, 1, 4, 2, 1, 1, 0, 0]

Largest Rectangle in a Histogram

Another important application is finding the largest rectangular area in a histogram. A monotonic increasing stack can identify the nearest smaller bars on either side of each bar, which determines how far that bar can extend.

This problem demonstrates that monotonic stacks are not limited to simply finding the next greater or smaller value. They can also maintain boundaries for more complex range calculations.

Why the Algorithm Is O(n)

The nested while loop can initially look like it makes the algorithm quadratic. However, every element is pushed onto the stack at most once and popped at most once.

Therefore, across the entire traversal, the total number of stack operations is proportional to n.

Complexity

Time: O(n)
Auxiliary space: O(n)

When Should You Think of a Monotonic Stack?

A monotonic stack is worth considering when a problem has a combination of the following characteristics:

  • The input is a sequence or array.
  • You need the closest element to the left or right.
  • The question asks for greater or smaller relationships.
  • A naive solution would repeatedly scan to the left or right.
  • Previously encountered elements become irrelevant after a new value appears.

Monotonic Stack vs Regular Stack

Regular Stack Monotonic Stack
Primarily follows LIFO behavior Follows LIFO behavior and maintains an order
No required relationship between values Values follow increasing or decreasing order
Used for general stack problems Often used for nearest greater/smaller problems

Common Mistakes

  • Choosing the wrong increasing or decreasing stack pattern.
  • Using the wrong comparison operator when removing elements.
  • Confusing next elements with previous elements.
  • Storing values when indexes are required to calculate distances.
  • Forgetting to process or interpret elements remaining in the stack.
  • Assuming a nested while loop automatically means O(n²) complexity.
  • Failing to test duplicate values, which can expose incorrect equality handling.

Key Takeaways

  • A monotonic stack maintains elements in increasing or decreasing order.
  • It is commonly used for next greater, next smaller, previous greater, and previous smaller problems.
  • Elements that cannot become useful candidates are removed from the stack.
  • Each element is generally pushed and popped at most once.
  • Many monotonic stack algorithms run in O(n) time.
  • Store indexes instead of values when the problem requires distances or positions.
  • Recognizing the nearest-greater or nearest-smaller pattern is the key to using this technique effectively.

Code Examples

const numbers = [2, 1, 2, 4, 3];

const result = new Array(numbers.length).fill(-1);
const stack = [];

for (let i = numbers.length - 1; i >= 0; i--) {
  while (
    stack.length > 0 &&
    stack[stack.length - 1] <= numbers[i]
  ) {
    stack.pop();
  }

  if (stack.length > 0) {
    result[i] = stack[stack.length - 1];
  }

  stack.push(numbers[i]);
}

console.log(result);
// [4, 2, 4, -1, -1]
    
const numbers = [4, 5, 2, 10, 8];

const result = new Array(numbers.length).fill(-1);
const stack = [];

for (let i = numbers.length - 1; i >= 0; i--) {
  while (
    stack.length > 0 &&
    stack[stack.length - 1] >= numbers[i]
  ) {
    stack.pop();
  }

  if (stack.length > 0) {
    result[i] = stack[stack.length - 1];
  }

  stack.push(numbers[i]);
}

console.log(result);
// [2, 2, -1, 8, -1]
    
const temperatures = [73, 74, 75, 71, 69, 72, 76, 73];

const answer = new Array(temperatures.length).fill(0);
const stack = [];

for (let i = 0; i < temperatures.length; i++) {
  while (
    stack.length > 0 &&
    temperatures[i] > temperatures[stack[stack.length - 1]]
  ) {
    const previousIndex = stack.pop();
    answer[previousIndex] = i - previousIndex;
  }

  stack.push(i);
}

console.log(answer);
// [1, 1, 4, 2, 1, 1, 0, 0]
    
const numbers = [4, 5, 2, 10, 8];

const result = new Array(numbers.length).fill(-1);
const stack = [];

for (let i = 0; i < numbers.length; i++) {
  while (
    stack.length > 0 &&
    stack[stack.length - 1] >= numbers[i]
  ) {
    stack.pop();
  }

  if (stack.length > 0) {
    result[i] = stack[stack.length - 1];
  }

  stack.push(numbers[i]);
}

console.log(result);
// [-1, 4, -1, 2, 2]
    

Pro Tip

When you see a problem asking for the nearest greater or smaller element on the left or right, immediately consider a monotonic stack. Before coding, decide the direction of traversal, whether the stack should be increasing or decreasing, and whether you need to store values or indexes.

🎨 IgKnight Docs

Master HTML, CSS, JS, Python, .NET and more with easy-to-follow lessons and practical code examples.

© 2025 IgKnight Learning Platform. All rights reserved.