Your monotonic stack solution isn't working. You've stared at the code for 20 minutes. The bug is invisible.
This is the debugging nightmare every developer faces with monotonic stacks. The logic seems right, but the output is wrong. Where's the bug?
This guide will teach you a systematic debugging approach that works every time: how to trace stack state, identify common bug patterns, verify invariants, and fix solutions in under 5 minutes.
TL;DR
The systematic debugging process:
- Verify monotonicity direction (increasing vs decreasing)
- Check popping condition (
<vs>vs<=vs>=) - Trace stack state (print at each iteration)
- Verify result initialization (pre-allocated with defaults)
- Check boundary conditions (empty array, single element)
Quick debug template:
for i in range(n):
print(f"i={i}, val={nums[i]}, stack={stack}") # Debug print
while stack and condition:
# ...
print(f" After: stack={stack}, result={result}") # Debug printThe Debugging Framework
Step 1: Verify Monotonicity Direction
Question: Should the stack be increasing or decreasing?
Rule:
- Next GREATER → Decreasing stack
- Next SMALLER → Increasing stack
Check:
# For next greater element:
# Stack should be decreasing (large at bottom, small at top)
# Example: After processing [5, 3, 1]
# Correct: stack = [5, 3, 1] (decreasing ✓)
# Wrong: stack = [1, 3, 5] (increasing ✗)How to verify:
# Add assertion after each push
stack.append(i)
# Verify decreasing order (for next greater)
if len(stack) >= 2:
assert nums[stack[-2]] > nums[stack[-1]], "Stack not decreasing!"Step 2: Check Popping Condition
Common bug: Using wrong comparison operator
Checklist:
Next GREATER element:
✓ while stack and nums[i] > nums[stack[-1]]
✗ while stack and nums[i] >= nums[stack[-1]] (too aggressive)
✗ while stack and nums[i] < nums[stack[-1]] (wrong direction)
Next SMALLER element:
✓ while stack and nums[i] < nums[stack[-1]]
✗ while stack and nums[i] <= nums[stack[-1]] (too aggressive)
✗ while stack and nums[i] > nums[stack[-1]] (wrong direction)Debug technique:
while stack and nums[i] > nums[stack[-1]]:
print(f" Popping {stack[-1]} (val={nums[stack[-1]]}) because {nums[i]} > {nums[stack[-1]]}")
idx = stack.pop()
result[idx] = nums[i]Step 3: Trace Stack State
The most powerful debugging technique: Print stack state at each iteration.
Template:
def nextGreaterElement_debug(nums):
n = len(nums)
result = [-1] * n
stack = []
print("=== Starting ===")
for i in range(n):
print(f"\ni={i}, val={nums[i]}")
print(f" Before: stack={stack}")
while stack and nums[i] > nums[stack[-1]]:
idx = stack.pop()
result[idx] = nums[i]
print(f" Popped {idx}, set result[{idx}]={nums[i]}")
stack.append(i)
print(f" After: stack={stack}")
print(f" Result so far: {result}")
print(f"\nFinal result: {result}")
return result
# Test
nextGreaterElement_debug([2, 1, 2, 4, 3])Output:
=== Starting ===
i=0, val=2
Before: stack=[]
After: stack=[0]
Result so far: [-1, -1, -1, -1, -1]
i=1, val=1
Before: stack=[0]
After: stack=[0, 1]
Result so far: [-1, -1, -1, -1, -1]
i=2, val=2
Before: stack=[0, 1]
Popped 1, set result[1]=2
After: stack=[0, 2]
Result so far: [-1, 2, -1, -1, -1]
i=3, val=4
Before: stack=[0, 2]
Popped 2, set result[2]=4
Popped 0, set result[0]=4
After: stack=[3]
Result so far: [4, 2, 4, -1, -1]
i=4, val=3
Before: stack=[3]
After: stack=[3, 4]
Result so far: [4, 2, 4, -1, -1]
Final result: [4, 2, 4, -1, -1]Step 4: Verify Result Initialization
Common bug: Not pre-initializing result array
Check:
# ✗ Wrong
result = [] # Empty list causes IndexError
# ✓ Correct
result = [-1] * n # Pre-allocated with defaultDebug technique:
# Verify size
assert len(result) == len(nums), f"Result size mismatch: {len(result)} vs {len(nums)}"
# Verify all initialized
assert all(r == -1 for r in result), "Result not initialized to -1"Step 5: Check Boundary Conditions
Test these edge cases:
# Edge case 1: Empty array
assert nextGreaterElement([]) == []
# Edge case 2: Single element
assert nextGreaterElement([5]) == [-1]
# Edge case 3: Two elements
assert nextGreaterElement([1, 2]) == [2, -1]
assert nextGreaterElement([2, 1]) == [-1, -1]
# Edge case 4: All same
assert nextGreaterElement([3, 3, 3]) == [-1, -1, -1]
# Edge case 5: Strictly increasing
assert nextGreaterElement([1, 2, 3, 4]) == [2, 3, 4, -1]
# Edge case 6: Strictly decreasing
assert nextGreaterElement([4, 3, 2, 1]) == [-1, -1, -1, -1]Common Bug Patterns
Bug Pattern 1: Off-by-One in Index
Symptom: IndexError or wrong results
Example:
# ✗ Wrong
for i in range(len(nums) - 1): # Misses last element!
# ...
# ✓ Correct
for i in range(len(nums)): # Includes all elements
# ...Bug Pattern 2: Wrong Stack Content
Symptom: Stack contains values instead of indices (or vice versa)
Example:
# ✗ Wrong: Storing values
stack.append(nums[i]) # Can't calculate distance later!
# ✓ Correct: Storing indices
stack.append(i) # Can access nums[stack[-1]] and calculate i - stack[-1]Debug check:
# Verify stack contains indices
if stack:
assert all(0 <= idx < len(nums) for idx in stack), "Stack contains invalid indices"Bug Pattern 3: Not Handling Duplicates
Symptom: Wrong results on arrays with duplicate values
Example:
# For contribution technique:
# ✗ Wrong: Symmetric comparison
while stack and arr[i] < arr[stack[-1]]: # Right
while stack and arr[i] < arr[stack[-1]]: # Left (same!)
# ✓ Correct: Asymmetric comparison
while stack and arr[i] < arr[stack[-1]]: # Right: strict
while stack and arr[i] <= arr[stack[-1]]: # Left: non-strictBug Pattern 4: Circular Array Single Pass
Symptom: Wrong results on circular array problems
Example:
# ✗ Wrong: Single pass
for i in range(n):
# ...
# ✓ Correct: Double pass
for i in range(2 * n):
idx = i % n
# ...
if i < n:
stack.append(idx)Debugging Workflow
When Your Solution Fails
Step 1: Identify the failing test case
# Find the smallest failing case
test_cases = [
[2, 1, 2, 4, 3],
[1, 2, 3],
[3, 2, 1],
[2, 2, 2],
]
for tc in test_cases:
result = nextGreaterElement(tc)
expected = compute_expected(tc)
if result != expected:
print(f"FAILED: {tc}")
print(f" Got: {result}")
print(f" Expected: {expected}")
breakStep 2: Add debug prints
# Add prints at key points
for i in range(n):
print(f"i={i}, val={nums[i]}, stack={stack}")
while stack and nums[i] > nums[stack[-1]]:
idx = stack.pop()
print(f" Pop {idx}, set result[{idx}]={nums[i]}")
result[idx] = nums[i]
stack.append(i)Step 3: Trace by hand
Write out each iteration:
i=0: stack=[], push 0, stack=[0]
i=1: stack=[0], 1 < 2, push 1, stack=[0,1]
i=2: stack=[0,1], 2 > 1, pop 1, result[1]=2, ...Step 4: Compare with expected
# At each step, verify against expected behavior
expected_stack_states = [
[0],
[0, 1],
[0, 2],
# ...
]
for i, expected_stack in enumerate(expected_stack_states):
actual_stack = get_stack_at_iteration(i)
assert actual_stack == expected_stack, f"Stack mismatch at i={i}"Step 5: Fix and verify
# After fixing, test all edge cases
test_all_edge_cases()Visualization Technique
ASCII Art Stack Trace
def visualize_stack(nums, stack, i):
"""
Print ASCII visualization of stack state.
"""
print(f"\nIteration i={i}, processing nums[{i}]={nums[i]}")
print("Stack (bottom to top):")
if not stack:
print(" (empty)")
else:
for idx in stack:
print(f" [{idx}] = {nums[idx]}")
print()
# Usage
for i in range(len(nums)):
visualize_stack(nums, stack, i)
# ... process ...Output:
Iteration i=2, processing nums[2]=2
Stack (bottom to top):
[0] = 2
[1] = 1
Iteration i=3, processing nums[3]=4
Stack (bottom to top):
[0] = 2
[2] = 2Checklist: Before Submitting
-
Monotonicity: Stack maintains correct order (increasing/decreasing)
-
Comparison: Using correct operator (
<,>,<=,>=) -
Initialization: Result array pre-allocated with defaults
-
Loop direction: Correct for problem (left-to-right vs right-to-left)
-
Circular handling: Double pass if array is circular
-
Edge cases: Tested empty, single, duplicates, increasing, decreasing
-
Index vs value: Storing correct type in stack
-
Boundary check: No IndexError on edge cases
Quick Debug Commands
# 1. Print stack at each iteration
print(f"Stack: {stack}")
# 2. Print stack with values
print(f"Stack: {[(i, nums[i]) for i in stack]}")
# 3. Verify monotonicity
if len(stack) >= 2:
print(f"Monotonic? {nums[stack[-2]]} > {nums[stack[-1]]}")
# 4. Print result updates
print(f"Set result[{idx}] = {nums[i]}")
# 5. Verify result array
print(f"Result: {result}")Conclusion
Debugging monotonic stack problems is systematic, not magical.
The process:
- Verify direction: Increasing vs decreasing
- Check comparison:
<vs>vs<=vs>= - Trace state: Print stack at each iteration
- Verify initialization: Pre-allocated result array
- Test boundaries: Edge cases
The debugging template:
for i in range(n):
print(f"i={i}, val={nums[i]}, stack={stack}")
while stack and condition:
idx = stack.pop()
print(f" Popped {idx}")
result[idx] = nums[i]
stack.append(i)
print(f" Result: {result}")Common bugs:
- Wrong monotonicity direction
- Wrong comparison operator
- Not initializing result array
- Storing values instead of indices
- Not handling duplicates (contribution technique)
Master this debugging approach, and you'll fix any monotonic stack bug in under 5 minutes.
Next steps:
Next time your solution fails, follow this systematic process.
Want to Practice LeetCode Smarter?
LeetCopilot is a free browser extension that enhances your LeetCode practice with AI-powered hints, personalized study notes, and realistic mock interviews — all designed to accelerate your coding interview preparation.
Also compatible with Edge, Brave, and Opera
