Next Greater Element Explained: Stack-Based Solution with Real Examples
Every time a stock trader asks 'when will this stock price exceed today's value again?' or a weather app calculates 'how many days until a warmer day?', they're solving a variant of the Next Greater Element problem. It's not an academic curiosity — it's a pattern that shows up constantly in time-series analysis, histogram calculations, and real-time data stream processing. If you've ever felt like brute-force nested loops were the only way to compare elements across an array, this article will change how you think about sequential data.
The core problem is this: given an array of numbers, for each element find the first element to its right that is strictly greater than it. The naive approach — check every element against every other element to its right — costs O(n²) time. With millions of data points, that's catastrophically slow. The stack-based solution does the same job in a single pass, O(n) time, by remembering only the elements that are still 'waiting' to find their greater neighbour.
By the end of this article you'll understand not just how the monotonic stack solution works, but exactly why the stack is the right data structure here, when to reach for this pattern in the wild, how to handle circular arrays (a classic interview twist), and which gotchas trip up even experienced developers. You'll walk away with fully runnable Java code, a clear mental model, and the confidence to answer any interview variant of this question.
The Brute-Force Baseline — And Why It Breaks Down
Before reaching for the elegant solution, let's be honest about the naive one. For each element at index i, you scan every element to its right until you find one that's larger. The moment you find it, that's your answer. If you reach the end without finding one, the answer is -1.
This works perfectly for small arrays. Write it once and you'll understand the problem deeply — that's actually valuable. The problem is cost. For an array of size n, the worst case is a descending array like [5, 4, 3, 2, 1]. Every element scans all the way to the right and finds nothing. That's n + (n-1) + (n-2) + ... + 1 comparisons — O(n²) time.
At 10,000 elements that means up to 100 million comparisons. At 1 million elements (totally normal for financial data), you're looking at a trillion operations. The brute force doesn't just 'run slow' — it becomes completely unusable. This is the exact moment where understanding data structures stops being academic and starts being a professional skill.
public class NextGreaterElementBruteForce { /** * For each element, scan rightward to find the first element * that is strictly greater. Returns -1 if none exists. * * Time: O(n^2) — nested loops over the array * Space: O(n) — result array */ public static int[] findNextGreaterBruteForce(int[] prices) { int totalElements = prices.length; int[] nextGreater = new int[totalElements]; for (int currentIndex = 0; currentIndex < totalElements; currentIndex++) { // Assume no greater element exists to the right nextGreater[currentIndex] = -1; // Scan every element to the right of currentIndex for (int lookAheadIndex = currentIndex + 1; lookAheadIndex < totalElements; lookAheadIndex++) { if (prices[lookAheadIndex] > prices[currentIndex]) { // Found the first greater element — record it and stop scanning nextGreater[currentIndex] = prices[lookAheadIndex]; break; } } } return nextGreater; } public static void main(String[] args) { int[] stockPrices = {73, 74, 75, 71, 69, 72, 76, 73}; int[] result = findNextGreaterBruteForce(stockPrices); System.out.println("Stock Price → Next Greater Price"); System.out.println("-------------------------------------"); for (int i = 0; i < stockPrices.length; i++) { String nextVal = result[i] == -1 ? "none" : String.valueOf(result[i]); System.out.printf(" %-6d → %s%n", stockPrices[i], nextVal); } } }
-------------------------------------
73 → 74
74 → 75
75 → 76
71 → 72
69 → 72
72 → 76
76 → none
73 → none
The Monotonic Stack — One Pass, O(n) Time
Here's the insight that makes everything click: when you're scanning left to right and you encounter a new element, it might be the 'next greater' answer for several previous elements simultaneously. The brute force misses this — it answers one question at a time. The stack lets you answer many questions at once.
The stack stores elements that are 'waiting' — they've been seen, but haven't found their next greater neighbour yet. Crucially, the stack stays in decreasing order from bottom to top (that's what makes it a monotonic stack). When a new element arrives: 1. While the stack isn't empty AND the new element is greater than the top of the stack — pop the top, and record the new element as its answer. 2. Push the new element onto the stack.
After one full pass, anything left in the stack never found a greater element, so their answer is -1.
Why does this work? Every element is pushed once and popped at most once. That's O(n) operations total, regardless of array shape. The stack ensures you never re-check a pair you've already resolved — you're spending zero effort on dead comparisons.
import java.util.Arrays; import java.util.Stack; public class NextGreaterElementStack { /** * Uses a monotonic decreasing stack to find the next greater element * for every position in a single left-to-right pass. * * Time: O(n) — each element is pushed and popped at most once * Space: O(n) — stack can hold at most n elements (e.g. descending input) */ public static int[] findNextGreater(int[] temperatures) { int totalDays = temperatures.length; int[] nextWarmerTemp = new int[totalDays]; // Fill with -1 as the default: "no warmer day found" Arrays.fill(nextWarmerTemp, -1); // Stack holds INDICES (not values) so we can update the result array Stack<Integer> waitingIndices = new Stack<>(); for (int today = 0; today < totalDays; today++) { // While today's temperature beats the temperature at the top of the stack... while (!waitingIndices.isEmpty() && temperatures[today] > temperatures[waitingIndices.peek()]) { // ...the day on top of the stack has found its answer: today's temperature int resolvedDay = waitingIndices.pop(); nextWarmerTemp[resolvedDay] = temperatures[today]; } // Today hasn't found ITS next greater yet — add it to the waiting list waitingIndices.push(today); } // Any index still in the stack had no greater element to its right — stays -1 return nextWarmerTemp; } public static void main(String[] args) { // Real-world framing: daily temperatures, find the next warmer day's temperature int[] dailyTemperatures = {73, 74, 75, 71, 69, 72, 76, 73}; int[] result = findNextGreater(dailyTemperatures); System.out.println("Day │ Temp │ Next Greater Temp"); System.out.println("─────┼──────┼──────────────────"); for (int day = 0; day < dailyTemperatures.length; day++) { String answer = result[day] == -1 ? "none" : String.valueOf(result[day]); System.out.printf(" %-3d │ %-3d │ %s%n", day + 1, dailyTemperatures[day], answer); } } }
─────┼──────┼──────────────────
1 │ 73 │ 74
2 │ 74 │ 75
3 │ 75 │ 76
4 │ 71 │ 72
5 │ 69 │ 72
6 │ 72 │ 76
7 │ 76 │ none
8 │ 73 │ none
The Circular Array Variant — The Interview Curveball
Once you've nailed the linear version, interviewers love throwing this at you: what if the array is circular? Meaning, after the last element, you wrap around to the first. For [5, 4, 3, 2, 1], the next greater element for 4 is 5 (which is at index 0, to the 'right' when wrapping around).
The trick is elegant: simulate two passes over the array by iterating from index 0 to 2n-1, using modulo to wrap the index. Critically, on the second pass you should NOT push new indices onto the stack — you're only resolving elements that are still waiting. A clean way to enforce this: only push during the first pass (i < n).
This technique — doubling the loop with modulo — is a classic pattern for any circular array problem, not just Next Greater Element. Internalise it and you'll use it again.
import java.util.Arrays; import java.util.Stack; public class NextGreaterElementCircular { /** * Finds the next greater element in a circular array. * Elements wrap around: after the last element, search continues from index 0. * * Technique: iterate 0 to 2n-1, use (index % n) to wrap around. * Only push indices during the first pass (index < n). * * Time: O(n) — each element is pushed/popped at most once across both passes * Space: O(n) — stack size */ public static int[] findNextGreaterCircular(int[] circularPrices) { int size = circularPrices.length; int[] nextGreater = new int[size]; Arrays.fill(nextGreater, -1); Stack<Integer> waitingIndices = new Stack<>(); // We loop 2*size to simulate wrapping around the array for (int iteration = 0; iteration < 2 * size; iteration++) { // Wrap the iteration index back into a valid array index int wrappedIndex = iteration % size; // Resolve all waiting elements that the current element is greater than while (!waitingIndices.isEmpty() && circularPrices[wrappedIndex] > circularPrices[waitingIndices.peek()]) { int resolvedIndex = waitingIndices.pop(); nextGreater[resolvedIndex] = circularPrices[wrappedIndex]; } // Only push during the first full pass — second pass is for resolution only if (iteration < size) { waitingIndices.push(wrappedIndex); } } return nextGreater; } public static void main(String[] args) { // In a circular market: after price[last], the search continues from price[0] int[] circularMarketPrices = {5, 4, 3, 2, 1}; int[] result = findNextGreaterCircular(circularMarketPrices); System.out.println("Circular Array: [5, 4, 3, 2, 1]"); System.out.println(); System.out.println("Price │ Next Greater (circular)"); System.out.println("───────┼──────────────────────────"); for (int i = 0; i < circularMarketPrices.length; i++) { String answer = result[i] == -1 ? "none" : String.valueOf(result[i]); System.out.printf(" %-4d │ %s%n", circularMarketPrices[i], answer); } } }
Price │ Next Greater (circular)
───────┼──────────────────────────
5 │ none
4 │ 5
3 │ 5
2 │ 5
1 │ 2
Next Greater Element Index Variant — Returning Distances, Not Values
LeetCode 739 'Daily Temperatures' is one of the most frequently asked Next Greater Element variants in real interviews. Instead of returning the next greater value, it asks: how many days until a warmer temperature? This is a small twist — instead of storing the value at the resolved index, you store the distance between the two indices.
This variant tests whether you truly understand what you're storing in the stack (indices, not values!) and whether you can adapt the pattern quickly. The stack logic is identical — the only change is one line in the result update.
This is also a great example of real-world application: think of it as 'wait time until the next event exceeds a threshold'. Used in financial analytics for breakout detection, in IoT sensor networks for anomaly detection, and in game development for AI aggression triggers.
import java.util.Arrays; import java.util.Stack; public class DaysUntilWarmerTemperature { /** * For each day, returns how many days you have to wait until a warmer temperature. * If no warmer temperature ever comes, the answer for that day is 0. * * This is LeetCode 739 — one of the most common Next Greater Element interview variants. * * Time: O(n) * Space: O(n) */ public static int[] daysUntilWarmer(int[] dailyTemps) { int totalDays = dailyTemps.length; int[] waitDays = new int[totalDays]; // default 0 means "never gets warmer" Stack<Integer> waitingDayIndices = new Stack<>(); for (int today = 0; today < totalDays; today++) { // Today's temp resolves all past days that were waiting for something warmer while (!waitingDayIndices.isEmpty() && dailyTemps[today] > dailyTemps[waitingDayIndices.peek()]) { int coldDay = waitingDayIndices.pop(); // KEY DIFFERENCE from the value variant: // Store the GAP between the two days, not the temperature itself waitDays[coldDay] = today - coldDay; } waitingDayIndices.push(today); } // Days still in the stack never found a warmer day — waitDays stays 0 return waitDays; } public static void main(String[] args) { int[] weekTemperatures = {73, 74, 75, 71, 69, 72, 76, 73}; int[] daysToWait = daysUntilWarmer(weekTemperatures); System.out.println("Day │ Temp │ Days Until Warmer"); System.out.println("─────┼──────┼───────────────────"); for (int day = 0; day < weekTemperatures.length; day++) { String wait = daysToWait[day] == 0 ? "never" : daysToWait[day] + " day(s)"; System.out.printf(" %-3d │ %-3d │ %s%n", day + 1, weekTemperatures[day], wait); } } }
─────┼──────┼───────────────────
1 │ 73 │ 1 day(s)
2 │ 74 │ 1 day(s)
3 │ 75 │ 4 day(s)
4 │ 71 │ 2 day(s)
5 │ 69 │ 1 day(s)
6 │ 72 │ 1 day(s)
7 │ 76 │ never
8 │ 73 │ never
| Aspect | Brute Force (Nested Loops) | Monotonic Stack (Single Pass) |
|---|---|---|
| Time Complexity | O(n²) — every element re-scanned | O(n) — each element pushed/popped once |
| Space Complexity | O(1) extra (result array excluded) | O(n) for the stack in worst case |
| Best Case Input | Already sorted ascending — still O(n²) | Any input — always O(n) |
| Worst Case Input | Descending array — n² comparisons | Descending array — all pushed, none popped mid-pass |
| Handles Circular Arrays | Yes, with a second loop pass | Yes, with 2n iteration and modulo |
| Code Complexity | Simple — easy to write, easy to read | Moderate — requires stack mental model |
| When to Use | Array size < 1,000, clarity matters most | Production code, interviews, large datasets |
| Interview Signal | Shows problem understanding | Shows data structure mastery |
🎯 Key Takeaways
- The monotonic stack works because it tracks 'unresolved' elements — the moment a new element resolves several past elements simultaneously is exactly where the O(n) efficiency comes from.
- Always push INDICES onto the stack, never raw values — duplicates in the array make value-based stacks ambiguous and incorrect.
- The circular array variant requires a 2n iteration with modulo, with pushes restricted to the first n iterations — this exact pattern reappears in other circular array problems.
- The 'days until warmer' (distance) variant and the 'next greater value' variant share 100% of their stack logic — the only difference is one line:
result[i] = valuevsresult[i] = today - i.
⚠ Common Mistakes to Avoid
- ✕Mistake 1: Pushing values instead of indices — Your result array update breaks with duplicate values (e.g. [3, 1, 3, 2] — which '3' do you update?) — Always push the array index. Get the value inside the loop with
array[index]when you need to compare. - ✕Mistake 2: Using wrong stack type (FIFO queue instead of LIFO stack) — You process elements in arrival order instead of recency order, so early elements never get resolved correctly and results are scrambled — Use
StackorDequewithpush/pop/peek. Never useQueuefor this pattern. - ✕Mistake 3: In the circular variant, pushing indices on both the first and second pass — Duplicate indices appear in the stack, causing some elements to be 'resolved' twice with wrong values, and others to remain unresolved — Add the guard
if (iteration < size)before the push statement so indices are only added during the first pass through the array.
Interview Questions on This Topic
- QCan you solve Next Greater Element in O(n) time? Walk me through your approach step by step, explaining what the stack contains at each point and why you chose a stack over a queue.
- QGiven a circular array, how does your Next Greater Element solution change? What's the minimum number of passes needed and why?
- QWhat happens to your stack-based solution if the input array contains all duplicate values, like [5, 5, 5, 5]? What does the stack look like at the end, and what does the output look like?
Frequently Asked Questions
What is the next greater element problem?
For each element in an array, the Next Greater Element is the first element to its right that is strictly larger. If no such element exists, the answer is -1. The challenge is finding all answers efficiently — ideally in O(n) time using a monotonic stack rather than O(n²) nested loops.
Why do we use a stack to solve the Next Greater Element problem?
A stack lets us batch-resolve multiple past elements the moment we encounter a new larger value. Without a stack, we'd re-scan all previous elements for every new element. The stack's LIFO order naturally matches the 'most recently seen, still waiting' pattern — each element is pushed once and popped once, giving O(n) total time.
What is a monotonic stack and how is it different from a regular stack?
A monotonic stack is a regular stack where you enforce an ordering constraint — in this problem, the stack stays in decreasing order from bottom to top. It's not a different data structure; it's a usage pattern. You maintain the order by popping elements that violate it whenever a new element is pushed, and those pop events are exactly when you record your answers.
Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.