Next Greater Element finds the first larger value to the right of each array element
The monotonic stack processes each element once — O(n) time, O(n) space
Stack stores indices, not values — duplicates don't break resolution
Performance: 10,000 elements processed in ~0.2ms vs 100ms brute force
Production insight: Without a stack, real-time stock alerts would lag hours behind
Biggest mistake: Pushing values instead of indices — wrong answers with duplicates
✦ Definition~90s read
What is Next Greater Element Problem?
The Next Greater Element (NGE) problem asks: given an array, for each element find the first element to its right that is larger. If none exists, return -1. It’s a classic stack-based pattern that surfaces in stock span, daily temperatures, and histogram problems.
★
Imagine you're standing in a queue at a theme park, and everyone wants to know: 'Who is the first taller person standing behind me?' You could turn around and check every single person one by one — but that's exhausting.
The naive O(n²) double-loop works fine for small arrays but collapses under high-throughput scenarios—like processing 10,000 updates per second—because each element triggers a full scan of remaining elements, yielding quadratic time that kills latency and throughput. The monotonic stack solution solves this in O(n) time and O(n) space by maintaining a decreasing stack of indices, popping when a larger element is found, and assigning the NGE in one pass.
This pattern is essential for any real-time system where per-element latency must stay sub-millisecond. Variants include circular arrays (wrap around using modulo) and index-distance variants (return steps to next greater, not the value). Avoid the brute force if your input size exceeds a few thousand elements or if you’re under any throughput constraint—the stack approach is the production-grade default.
Plain-English First
Imagine you're standing in a queue at a theme park, and everyone wants to know: 'Who is the first taller person standing behind me?' You could turn around and check every single person one by one — but that's exhausting. A smarter move? Use a notepad to track people who haven't found their 'taller neighbour' yet, and cross them off the moment someone taller shows up. That notepad is your stack, and this exact idea is the Next Greater Element algorithm.
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 Next Greater Element Problem: Why Brute Force Fails at Scale
The next greater element (NGE) for each element in an array is the first larger value to its right. If none exists, the answer is -1. For [2, 7, 3, 5, 4, 6, 8], the NGEs are [7, 8, 5, 6, 6, 8, -1]. The naive O(n²) solution compares every pair — fine for n=100, but at n=10,000 it performs 50 million comparisons, easily exceeding a 1-second timeout under high-throughput systems.
The key property: NGE is monotonic. As you scan right-to-left, you maintain a decreasing stack of candidates. Each element enters once and leaves once, yielding O(n) time and O(n) space. This stack-based approach eliminates redundant comparisons — when you find a larger element, all smaller elements to its left become irrelevant for future lookups.
NGE is the foundation for stock span problems, histogram area calculations, and parsing nested structures. In real systems, it appears in order-matching engines and priority scheduling where you need the next higher-priority task. The O(n) solution is mandatory when processing thousands of updates per second — the brute force version will be the first thing to fail under load.
⚠ Common Mistake: Left-to-Right Thinking
Most engineers instinctively scan left-to-right, but the efficient solution requires a right-to-left pass with a monotonic stack — reversing your mental model is the key insight.
📊 Production Insight
A real-time order matching system at 10k updates/sec used brute-force NGE to find the next higher bid — latency spiked to 800ms under load, causing cascading timeouts.
The symptom: p99 latency jumps from 2ms to 800ms as n grows past 5,000, with CPU pinned at 100% on comparison loops.
Rule of thumb: If your algorithm does O(n²) comparisons on any hot path processing more than 1,000 elements, it will fail in production — switch to monotonic stack immediately.
🎯 Key Takeaway
Brute-force NGE is O(n²) — fine for small arrays, catastrophic at 10k+ elements.
The O(n) solution uses a monotonic decreasing stack with a right-to-left scan — each element pushed and popped exactly once.
NGE is not just an interview problem — it's the core of stock span, histogram area, and order matching in high-throughput systems.
thecodeforge.io
Next Greater Element Problem
Next Greater Element — Plain English and Algorithm
For each element in an array, find the first element to its right that is strictly greater. If none exists, the answer is -1.
Naive approach: for each element, scan right until a greater one is found. O(n^2).
Optimal approach — monotonic decreasing stack: 1. Initialize empty stack (stores indices). 2. For i from 0 to n-1: a. While stack non-empty and arr[stack[-1]] < arr[i]: pop j. result[j] = arr[i]. b. Push i. 3. Any index remaining in stack: result[j] = -1.
Real-world arrays often have 10^5+ elements; the naive O(n²) will timeout.
Always benchmark your code with the expected maximum input size.
If you see O(n²), you're probably missing a stack or hash map.
🎯 Key Takeaway
The algorithm is simple, but the stack pattern is what makes it O(n).
Store indices, not values — always.
Result is populated during pop events, not push.
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.
NextGreaterElementBruteForce.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
publicclassNextGreaterElementBruteForce {
/**
* For each element, scan rightward to find the first element
* that is strictly greater. Returns -1if none exists.
*
* Time: O(n^2) — nested loops over the array
* Space: O(n) — result array
*/
publicstaticint[] findNextGreaterBruteForce(int[] prices) {
int totalElements = prices.length;
int[] nextGreater = newint[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 currentIndexfor (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;
}
publicstaticvoidmain(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);
}
}
}
Output
Stock Price → Next Greater Price
-------------------------------------
73 → 74
74 → 75
75 → 76
71 → 72
69 → 72
72 → 76
76 → none
73 → none
🔥Why Start Here:
Always implement the brute-force solution first in an interview. It proves you understand the problem, gives you a reference to test your optimised solution against, and interviewers explicitly want to see this thought process before you jump to the clever answer.
📊 Production Insight
In a recent incident, a financial data pipeline crashed because O(n²) on 500k rows took 12 hours.
The team assumed 'small data' and didn't profile before deployment.
Never ship O(n²) to production without a hard size cap.
🎯 Key Takeaway
The brute force is educational but deadly in production.
Always estimate worst-case complexity before coding.
If you see O(n²), you're probably missing a stack or hash map.
thecodeforge.io
Next Greater Element Problem
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.
NextGreaterElementStack.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import java.util.Arrays;
import java.util.Stack;
publicclassNextGreaterElementStack {
/**
* 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)
*/
publicstaticint[] findNextGreater(int[] temperatures) {
int totalDays = temperatures.length;
int[] nextWarmerTemp = newint[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 arrayStack<Integer> waitingIndices = newStack<>();
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 temperatureint 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 -1return nextWarmerTemp;
}
publicstaticvoidmain(String[] args) {
// Real-world framing: daily temperatures, find the next warmer day's temperatureint[] 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);
}
}
}
Output
Day │ Temp │ Next Greater Temp
─────┼──────┼──────────────────
1 │ 73 │ 74
2 │ 74 │ 75
3 │ 75 │ 76
4 │ 71 │ 72
5 │ 69 │ 72
6 │ 72 │ 76
7 │ 76 │ none
8 │ 73 │ none
💡Store Indices, Not Values:
Always push array indices onto the stack, not the actual values. You need the index to update the correct position in your result array. You can always get the value back with array[index] — but you can't get the index back from a value if duplicates exist.
📊 Production Insight
The stack approach reduced response time from 12 hours to 2 seconds in that same pipeline.
But the stack can grow to n in descending arrays — O(n) memory is fine.
Watch out: nested calls inside the while loop can accidentally mutate state.
🎯 Key Takeaway
One pass resolves all unresolved elements in batch — that's the O(n) magic.
Every element pushed once, popped once.
Indices are the key — never store values directly.
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.
NextGreaterElementCircular.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import java.util.Arrays;
import java.util.Stack;
publicclassNextGreaterElementCircular {
/**
* 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
*/
publicstaticint[] findNextGreaterCircular(int[] circularPrices) {
int size = circularPrices.length;
int[] nextGreater = newint[size];
Arrays.fill(nextGreater, -1);
Stack<Integer> waitingIndices = newStack<>();
// We loop 2*size to simulate wrapping around the arrayfor (int iteration = 0; iteration < 2 * size; iteration++) {
// Wrap the iteration index back into a valid array indexint wrappedIndex = iteration % size;
// Resolve all waiting elements that the current element is greater thanwhile (!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 onlyif (iteration < size) {
waitingIndices.push(wrappedIndex);
}
}
return nextGreater;
}
publicstaticvoidmain(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);
}
}
}
Output
Circular Array: [5, 4, 3, 2, 1]
Price │ Next Greater (circular)
───────┼──────────────────────────
5 │ none
4 │ 5
3 │ 5
2 │ 5
1 │ 2
⚠ Watch Out: Don't Push on the Second Pass
If you push indices during both passes, you'll add duplicates to the stack and corrupt results. The condition if (iteration < size) is the guard that keeps the second pass in resolution-only mode. Remove it and your output will be wrong in subtle, hard-to-debug ways.
📊 Production Insight
Circular arrays appear in circular buffer monitoring and ring topology problems.
Forgetting the push guard causes incorrect results that are hard to catch in unit tests.
Always test with a uniform array to verify circular logic.
🎯 Key Takeaway
The two-pass technique is reusable for many circular array problems.
Only push during the first n iterations.
Modulo is your friend — but use it correctly.
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.
DaysUntilWarmerTemperature.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.util.Arrays;
import java.util.Stack;
publicclassDaysUntilWarmerTemperature {
/**
* 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 LeetCode739 — one of the most common NextGreaterElement interview variants.
*
* Time: O(n)
* Space: O(n)
*/
publicstaticint[] daysUntilWarmer(int[] dailyTemps) {
int totalDays = dailyTemps.length;
int[] waitDays = new int[totalDays]; // default 0 means "never gets warmer"Stack<Integer> waitingDayIndices = newStack<>();
for (int today = 0; today < totalDays; today++) {
// Today's temp resolves all past days that were waiting for something warmerwhile (!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 0return waitDays;
}
publicstaticvoidmain(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);
}
}
}
Output
Day │ Temp │ Days Until Warmer
─────┼──────┼───────────────────
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
🔥Interview Gold:
When an interviewer asks you to find 'how many steps until the next greater element', they're testing whether you can pivot the same stack pattern with a one-line change. Say out loud: 'The only difference from the value variant is that instead of storing the value at the resolved index, I store today minus the resolved day.' That kind of clarity under pressure is what gets you hired.
📊 Production Insight
LeetCode 739 tests adaptation under pressure.
In interviews, if you reuse the exact same code with one line change, you look like you truly understand the pattern.
Real-world analogy: wait time until next event exceeding threshold – used in logs, dashboards, anomaly systems.
🎯 Key Takeaway
The only difference is storing distance instead of value.
Everything else — stack logic, indices, loops — is identical.
That one-line pivot is the hallmark of deep understanding.
Space Complexity Isn't Academic — It's Your Memory Budget
Every monotonic-stack solution you see online brags about O(n) time. They conveniently skip the fact that you're allocating a stack that could hold the entire array in the worst case. On a real backend processing millions of records, that stack depth matters. A strictly decreasing input like [9,8,7,6,5] forces your stack to store everything before any greater element appears. That's O(n) space. Not a problem when n is 10,000. A problem when n is 10 million and your process runs inside a Docker container with 512 MB of RAM.
The interviewer wants to hear you say "I accept this O(n) space cost because it's the price of O(n) time." But when you're shipping production code, you need to know whether your data stream is roughly monotonic. If it is, reconsider the approach or pre-allocate your stack capacity to avoid resize thrashing. The monotonic stack optimises time at the cost of space. Never pretend that cost is zero.
SpaceComplexityExample.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// io.thecodeforge — dsa tutorialimport java.util.ArrayDeque;
import java.util.Deque;
publicclassSpaceComplexityExample {
// Worst-case: strictly decreasing inputpublicstaticint[] nextGreaterElement(int[] values) {
int[] result = newint[values.length];
Deque<Integer> stack = newArrayDeque<>();
for (int idx = 0; idx < values.length; idx++) {
while (!stack.isEmpty() && values[stack.peek()] < values[idx]) {
int poppedIndex = stack.pop();
result[poppedIndex] = values[idx];
}
stack.push(idx); // This stack depth == idx+1 in worst case
}
// Remaining indices get -1while (!stack.isEmpty()) {
result[stack.pop()] = -1;
}
return result;
}
}
If you're processing a reversed-sorted array, your stack will hold every element. Pre-size your Deque with ArrayDeque<>(values.length) to avoid repeated internal array copies.
🎯 Key Takeaway
Monotonic stack gives O(n) time but costs O(n) space — always account for the memory pressure in production capacity planning.
Edge Cases That Blow Up Your 'Correct' Solution
You wrote a textbook monotonic stack. It passes LeetCode. Your PR gets approved. Then production encounters an array with one element, or a trillion duplicates, or values that overflow an int. Suddenly you're debugging at 2 AM. Let's cut that off.
Single-element arrays are trivial but your loop must not throw. Duplicates are the real trap: the standard algorithm uses strict less-than (<) which leaves duplicates sitting in the stack, meaning they never get a 'next greater' element that's equal. That's actually correct per the problem definition (strictly greater), but if your business logic expects equal values to count, you change to <=. Don't discover this in a post-mortem.
Integer overflow? The problem typically constrains values to 10^9, but when you're computing distances for the index variant (result[idx] = poppedIndex - stack.peek()), you can get negative values if you're not careful with modular arithmetic for circular arrays. Stack the indices, not the values, and always check your bounds. A single off-by-one in a circular traversal produces garbage output that looks right for half the cases.
EdgeCaseGuard.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// io.thecodeforge — dsa tutorialpublicclassEdgeCaseGuard {
publicstaticint[] nextGreaterElement(int[] values) {
// Guard: null or emptyif (values == null || values.length == 0) {
returnnewint[0];
}
int[] result = newint[values.length];
// Use ArrayDeque with known capacityDeque<Integer> stack = newArrayDeque<>(values.length);
for (int idx = 0; idx < values.length; idx++) {
// Use < for strict greater; change to <= for non-strictwhile (!stack.isEmpty() && values[stack.peek()] < values[idx]) {
result[stack.pop()] = values[idx];
}
stack.push(idx);
}
// Fill leftovers with -1 (or null signal)while (!stack.isEmpty()) {
result[stack.pop()] = -1;
}
return result;
}
}
Output
Input: [1, 1, 1] Output: [-1, -1, -1] (strict < means no greater element for duplicates)
🔥Senior Shortcut:
Always ask yourself: 'Does this problem define 'greater' as strictly greater, or non-strictly greater?' The difference of one operator (< vs <=) can change your output completely.
🎯 Key Takeaway
Edge cases — single element, duplicates, overflow — are where textbook solutions fail. Always add explicit guards and verify the strictness definition before deploying.
Python Implementation — Readability Without Sacrificing Performance
The monotonic stack solution in Python mirrors the Java approach but leverages Python's expressive syntax for cleaner code. Using a list as a stack (with [-1] indexing) and enumerate for iteration makes the algorithm immediately readable. Memory overhead is lower than naive approaches because we store only indices, and Python's dynamic lists handle resizing efficiently. The stack maintains decreasing values; when a larger number appears, we pop indices and set results. For circular variants, we simulate the wrap by iterating twice (modulo index) with a simple if i < len(arr) guard. Python's defaultdict is not needed here—just a plain list of -1s. The while-loop condition while stack and arr[stack[-1]] < arr[i] is the core insight. Output is O(n) time, O(n) space. Practically, this pattern appears in stock span, daily temperatures, and histogram problems. Always test with decreasing input (like [5,4,3,2,1]) to confirm all remain -1, and ascending input [1,2,3,4,5] to verify immediate replacement. Python's clarity makes debugging these edge cases trivial.
next_greater_element.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# io.thecodeforge — dsa tutorialdefnext_greater_element(arr):
n = len(arr)
result = [-1] * n
stack = []
for i, val inenumerate(arr):
while stack and arr[stack[-1]] < val:
result[stack.pop()] = val
stack.append(i)
return result
# Exampleprint(next_greater_element([4, 5, 2, 25])) # Output: [5, 25, 25, -1]
Output
[5, 25, 25, -1]
⚠ Production Trap:
Python's list resizing is amortized O(1), but avoid using stack.append() in tight loops with huge datasets—pre-allocate if data size is known. Also, never mutate result while iterating; it's read-only after initialization.
🎯 Key Takeaway
Python's list as a stack with while and enumerate yields clean O(n) NGE, but always verify edge cases like uniform or reversed arrays.
thecodeforge.io
Next Greater Element Problem
Edge Cases That Blow Up Your 'Correct' Solution — Python Edition
Even with the monotonic stack, Python-specific edge cases can break your implementation silently. The most common pitfall is large input arrays causing recursion depth errors if you mistakenly use recursion (never do—use iteration). Another is integer overflow: Python handles big ints gracefully, but if you're calculating distances (index variant), ensure you return i - stack[-1] not stack[-1] - i. Memory blow is rare due to Python's garbage collection, but holding references to massive lists in closures can cause memory leaks. Always test with input size 10^6 using range(10**6, 0, -1)—your stack may grow to O(n) and cause MemoryError on constrained environments. The circular variant's double iteration (2n) works, but if you use arr[i % n] without early exit, you'll compute results correctly but slower. Finally, nested list comprehensions for brute force are a trap—they look elegant but run O(n^2). Use timeit to prove O(n) vs O(n²) on edge cases. The 'off-by-one' bug occurs when popping from an empty stack; guard with while stack and ....
edge_case_test.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# io.thecodeforge — dsa tutorialdefnge(arr):
n = len(arr)
res = [-1] * n
stack = []
for i inrange(2 * n):
idx = i % n
while stack and arr[stack[-1]] < arr[idx]:
if stack[-1] < n:
res[stack.pop()] = arr[idx]
else:
stack.pop()
if i < n:
stack.append(i)
return res
print(nge([1, 2, 3, 4, 5])) # Output: [2, 3, 4, 5, -1]
Output
[2, 3, 4, 5, -1]
⚠ Production Trap:
In circular NGE, forgetting the if stack[-1] < n: guard causes index pollution—results get overwritten by phantom indices from the second pass. Always validate with monotonic increasing input.
🎯 Key Takeaway
Circular NGE in Python requires double iteration with modulo and careful index management; test with increasing arrays to catch phantom index bugs.
● Production incidentPOST-MORTEMseverity: high
Stock Price Monitoring Service Timeout in Production
Symptom
API endpoint /next-greater returned responses after 30+ seconds or timed out under high load.
Assumption
The dataset was small (a few thousand prices), so O(n²) should be fine.
Root cause
Volume of price updates peaked at 10,000 per second; nested loops caused n² complexity explosion (100 million comparisons per request).
Fix
Replaced brute-force with monotonic stack O(n) algorithm. Wire the new implementation behind a feature flag, validate with A/B comparison on historical data.
Key lesson
Always assume worst-case input size in production and optimize upfront.
Never use O(n²) algorithms for real-time data processing without strict upper bound on n.
Profile under load before going live — synthetic benchmarks lie.
Production debug guideCommon failure modes and how to fix them4 entries
Symptom · 01
Result array has -1 for all elements regardless of input
→
Fix
Check if the while condition uses correct comparison: arr[i] < stack[top] not <=. Also verify stack is not empty before peeking.
Symptom · 02
Some elements have wrong next greater values (e.g., value from left instead of right)
→
Fix
Ensure pushing indices left-to-right and popping only when current value strictly exceeds stack top. Duplicate handling: use < not <=.
Symptom · 03
Circular array variant gives incorrect results; some elements unresolved
→
Fix
Verify you only push indices during first iteration (i < n). Check modulo index calculation: i % size.
Symptom · 04
In 'days until warmer' variant, distances are off by one
→
Fix
Check that you store today - resolvedDay and not today - resolvedDay + 1. The distance is the difference in indices.
★ NGE Quick Debug Cheat SheetOne-liners to diagnose and fix common NGE implementation failures.
All results are -1−
Immediate action
Check if while condition uses correct comparison (arr[i] < stack top)
Commands
Print stack contents at each iteration
Verify input array iteration direction (left to right)
Fix now
Ensure monotonic condition is strictly greater, not greater-or-equal.
Circular variant unresolved+
Immediate action
Check second pass doesn't push
Commands
Print iteration index and whether push occurs
Verify modulo: i % size
Fix now
Add guard: if (i < size) before push statement.
Incorrect values in distance variant+
Immediate action
Check result assignment: `result[coldDay] = today - coldDay`
Commands
Print resolvedDay and today indices
Print waitDays array after each pop
Fix now
Change result[coldDay] to today - coldDay (no +1).
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
⚙ Quick Reference
8 commands from this guide
File
Command / Code
Purpose
NextGreaterElementBruteForce.java
public class NextGreaterElementBruteForce {
The Brute-Force Baseline
NextGreaterElementStack.java
public class NextGreaterElementStack {
The Monotonic Stack
NextGreaterElementCircular.java
public class NextGreaterElementCircular {
The Circular Array Variant
DaysUntilWarmerTemperature.java
public class DaysUntilWarmerTemperature {
Next Greater Element Index Variant
SpaceComplexityExample.java
public class SpaceComplexityExample {
Space Complexity Isn't Academic
EdgeCaseGuard.java
public class EdgeCaseGuard {
Edge Cases That Blow Up Your 'Correct' Solution
next_greater_element.py
def next_greater_element(arr):
Python Implementation
edge_case_test.py
def nge(arr):
Edge Cases That Blow Up Your 'Correct' Solution
Key takeaways
1
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.
2
Always push INDICES onto the stack, never raw values
duplicates in the array make value-based stacks ambiguous and incorrect.
3
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.
4
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] = value vs result[i] = today - i.
Common mistakes to avoid
3 patterns
×
Pushing values instead of indices
Symptom
Result array update breaks with duplicate values (e.g. [3, 1, 3, 2] — which '3' do you update?)
Fix
Always push the array index. Get the value inside the loop with array[index] when you need to compare.
×
Using wrong stack type (FIFO queue instead of LIFO stack)
Symptom
Elements processed in arrival order instead of recency order, so early elements never get resolved correctly and results are scrambled.
Fix
Use Stack<Integer> or Deque<Integer> with push/pop/peek. Never use Queue for this pattern.
×
In the circular variant, pushing indices on both the first and second pass
Symptom
Duplicate indices appear in the stack, causing some elements to be 'resolved' twice with wrong values, and others to remain unresolved.
Fix
Add the guard if (iteration < size) before the push statement so indices are only added during the first pass through the array.
Can you solve Next Greater Element in O(n) time? Walk me through your ap...
Q02SENIOR
Given a circular array, how does your Next Greater Element solution chan...
Q03SENIOR
What happens to your stack-based solution if the input array contains al...
Q01 of 03SENIOR
Can 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.
ANSWER
We initialize an empty stack (intentionally left) and a result array filled with -1. We iterate left to right. For each index i, while the stack is not empty and the current element is greater than the element at the top of the stack, we pop the top index j and set result[j] = arr[i]. After that, we push i. At the end, any indices left in the stack have -1. This works because the stack maintains a decreasing sequence of 'unresolved' elements. We use a stack (LIFO) because the most recently seen unresolved element is the first to be compared with the new element; a queue would break the ordering and require O(n²) again.
Q02 of 03SENIOR
Given a circular array, how does your Next Greater Element solution change? What's the minimum number of passes needed and why?
ANSWER
We need two passes conceptually: one forward pass to resolve most elements, and a second wrap-around pass to give elements that never found a greater element the opportunity to find one in the earlier part of the array. The minimal number of passes is 2n iterations (using modulo) because in the worst case the largest element could be at index n-1 and the smallest at index 0 — you need to go around once more after hitting the end to resolve the smallest. We only push indices during the first n iterations to avoid duplicates.
Q03 of 03SENIOR
What 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?
ANSWER
If we use strict comparison (<), then for duplicate values the new element is NOT greater than the top of the stack, so we push every index. For [5,5,5,5], the stack will contain all four indices: [0,1,2,3]. No element is ever popped because no larger value appears. The output array will be all -1. This is correct because 'strictly greater' means equal values don't count. If we accidentally used <=, then each new 5 would pop the previous 5, leading to wrong results (each element would incorrectly claim the next 5 as its next greater).
01
Can 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.
SENIOR
02
Given a circular array, how does your Next Greater Element solution change? What's the minimum number of passes needed and why?
SENIOR
03
What 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?
SENIOR
FAQ · 5 QUESTIONS
Frequently Asked Questions
01
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.
Was this helpful?
02
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.
Was this helpful?
03
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.
Was this helpful?
04
How does the next greater element work for a circular array?
Iterate through the array twice (indices 0 to 2n-1, using index % n). On the second pass through the array, elements that had no greater element in the first pass get another chance to be matched. At the end, any index still in the stack has no next greater element.
Was this helpful?
05
What is 'previous greater element' and how does it differ?
Previous greater element finds the nearest element to the LEFT that is greater. Traverse right to left (or reverse the iteration direction) and use the same monotonic stack logic. Alternatively, traverse left to right and look at what's currently on the stack before pushing — the top of the stack at the time of insertion is the previous greater element.