stacks & queues · study console
Stacks · Queues · Monotonic Stack

See through
the disguise.

The mechanics of push and pop are trivial. The depth is in recognizing when a problem is secretly a stack problem — and in building genuine intuition for monotonic stack, the pattern candidates can execute when told to, but rarely derive unprompted.

tier-1 OA + onsite calibration 3 interactive widgets 10 problems · easy → hard monotonic stack as centerpiece
§ 01 — mechanics that actually matter

Stack and queue in one pass

Both structures are discipline wrappers around an ordered collection. The discipline is what makes them powerful, not the data structure itself.

Stack — LIFOLast In, First Out. The most recently added element is the only one accessible. Operations: push (add to top), pop (remove from top), peek/top (read top without removing). In Python: use a plain list; append is push, pop() is pop. In all serious interview implementations: O(1) for both operations. The mental model is a stack of plates — you can only add or remove from the top.
Queue — FIFOFirst In, First Out. The element added earliest is the first one served. Operations: enqueue (add to back), dequeue (remove from front). In Python: use collections.deque; it gives O(1) append to either end. The mental model is a line at a checkout — first arrival, first served. In interviews, queues appear primarily as the engine behind BFS; the queue mechanics themselves are rarely tested in isolation at senior level.
When stack vs queue signals matterThe LIFO vs FIFO choice is almost always the problem's key constraint. A stack says "the most recent event is the most relevant." A queue says "order of arrival matters, process in the order received." Nested structures (brackets, HTML tags, function call frames) demand LIFO because what closed most recently must match what opened most recently. BFS demands FIFO because all nodes at depth d must be processed before depth d+1.
Queue in this topic's scopeFull BFS and graph traversal live in the Graphs topic. Here, queue mechanics themselves and queue-specific design problems — primarily "implement a queue using two stacks" — are covered. Monotonic deque (used in Sliding Window maximum) is addressed in the deque vs stack section below to sharpen a boundary that many candidates conflate.
the only interview rule you need

If a problem has a "most recent state" that governs what happens next — close the most recently opened bracket, undo the most recent operation, propagate a result to whoever pushed a frame most recently — reach for a stack. If it's about order of arrival, reach for a queue. Everything else is a variation on one of these two primitives.

§ 02 — deriving the pattern, not memorizing it

Start from the brute force

Canonical problem: given an array, find the next greater element (NGE) to the right of each position — the first element to the right that is strictly larger. Return -1 if none exists.

The O(n²) solution is obvious: for each element, scan right until you find a larger one or exhaust the array.

brute force · O(n²)
for i in 0..n:
    result[i] = -1
    for j in i+1..n:
        if arr[j] > arr[i]:
            result[i] = arr[j]
            break     # found NGE, stop inner scan

Now ask the diagnostic question: what is the brute force throwing away? Suppose you're scanning forward from index i and you reach index j where arr[j] > arr[i]. You just found i's NGE. But your scan also walked through every element between i+1 and j-1 — and for some of those elements, arr[j] is also their NGE. The brute force discards that realization and re-derives it from scratch in a later outer-loop iteration.

The question that derives the whole algorithm

Here it is, stated precisely: "When scanning right from i we find arr[j] > arr[i] — for every element between i and j that hasn't yet found its NGE, what does arr[j] tell us?"

Answer: if arr[j] is greater than any of those elements, it is their NGE too — because we're scanning left-to-right, so arr[j] is the first element to their right that beats them. We can settle all of their answers in that same moment, not separately.

This is the entire derivation. Keep a stack of indices whose NGE we haven't found yet. As we scan left to right, whenever we encounter a new element, it might be the NGE for several pending elements. Pop everything from the stack that it beats — those elements just found their answer. Then push the new element onto the stack as a new pending candidate.

the amortized O(n) argument

Each element is pushed onto the stack exactly once and popped at most once. Total pushes ≤ n, total pops ≤ n — so across the entire array pass, the total work is O(2n) = O(n) regardless of how many pops happen at any one step. This is the amortized argument: an expensive step (many pops) only happens because many cheap steps (pushes with no pops) preceded it. The total budget never exceeds 2n operations.

The key identity: the stack contains exactly the elements that are still candidates — elements that have not yet encountered a greater element to their right. The moment a new element arrives that beats some of them, those candidates are no longer needed. They've found their answer and can be discarded permanently — no element to the right of j could have been their NGE, because j arrived first.

the "useless element" argument

If element at index k is in the stack below a later-pushed element at index m where arr[m] < arr[k], then for any future element at index q > m: if arr[q] > arr[m], then since arr[k] > arr[m], we have arr[q] might or might not beat k. If arr[q] ≤ arr[k], it won't pop k either. So m acts as a gatekeeper: k can only be popped by something that also beats m. Elements in the stack will always be popped in top-to-bottom order — they'll never need reordering.

§ 03 — interactive step-through

Widget A — monotonic stack in motion

The array below runs through eight elements. Each press of Step processes one element: first all pops (red cells found their answer), then the push of the current element. Toggle between next-greater and next-smaller to see that only the pop condition changes — the structural discipline is identical.

Widget A · monotonic stack step-through

next greater / smaller
input array — arr
Stack (top at top)
Result array
? = no answer yet  ·  −1 = no answer exists
Press Step to begin. Monotonic stack finds next greater element in O(n).
processing now in stack (candidate) popped → answer found answered

Notice the structural symmetry when you toggle modes. Next-greater pops when top < current; next-smaller pops when top > current. The stack's invariant flips its direction, but the algorithm skeleton — pop-while-condition, push, record answers for popped elements — does not change at all.

§ 04 — the decision rule under pressure

Increasing vs decreasing: which to reach for

The terminology is sometimes used inconsistently across sources. Use this framing and you won't get confused:

Next Greater Element → pop when top < currentWhen the new element is larger than the stack top, the top has found its answer (= current). Pop it, record the answer. Keep popping while this holds. The stack after the pass is decreasing from bottom to top (larger values sit deeper; smaller values are more recent pushes). Mnemonic: "the greater newcomer evicts smaller predecessors."
Next Smaller Element → pop when top > currentSymmetric. The new element is smaller than the top, so the top has found its first-smaller-to-the-right. Pop, record. The stack ends up increasing from bottom to top. Mnemonic: "the smaller newcomer evicts larger predecessors."
interview pressure check — run this 3-element test

If you go blank on which direction, run the array [3, 1, 4] mentally. For NGE: 3's answer is 4, 1's answer is 4, 4 has none. For NSE: 3's answer is 1, 1 has none, 4 has none. Then ask: "which pop condition produces the right pops?" — NGE pops 3 and 1 when 4 arrives (4 > 1, then 4 > 3), so pop condition is top < current. NSE pops 3 when 1 arrives (1 < 3), so pop condition is top > current. This 3-element trace resolves the question in under 15 seconds.

A subtler variant: previous greater / previous smaller element (looking left, not right). For those, process the array right-to-left with the same pop conditions, or process left-to-right and answer the question "what was already in the stack when this element was pushed" rather than "what gets popped when this element arrives." The stack discipline is unchanged.

For problems like Largest Rectangle in Histogram, the answer involves both the previous smaller and next smaller element for each bar. You can compute both in one left-to-right pass by reading answers from the stack at pop time (the element being popped gets next-smaller = current element) and at push time (the element being pushed gets previous-smaller = new stack top after the push).

§ 05 — make the gap visceral

Widget B — brute force vs monotonic, racing

Array [6, 5, 4, 3, 2, 1, 7] is a near-worst case for the brute force: every element except the last must scan all the way to index 6 to find its answer. Watch the comparison counters diverge — the stack's counter barely moves until the last step, then settles everything at once. That final burst is the amortized argument made visual.

Widget B · comparison race

O(n²) vs O(n)
Brute force — forward scan
comparisons this step
total comparisons
0
Monotonic stack
comparisons this step
total comparisons
0
current (outer loop / current element) inner scanner / inner stack candidate popped / answered

The contrast at step 1 (outer element 6): BF does 6 comparisons scanning to the end. The stack does 0 — it just pushes. The stack's "debt" is paid at step 7, but it pays 6 comparisons total across all steps while BF pays 21. Each element in the stack is touched exactly twice: once pushed, once popped.

§ 06 — why a counter isn't enough

Nested structure validation

The classic nested-brackets problem — determine if a string of brackets is valid — is usually taught correctly, but the crucial motivating argument is often skipped: why a counter fails.

For a single bracket type, a counter works fine: increment on (, decrement on ), valid iff counter reaches 0 and never goes negative. But consider two bracket types and the string "([)]":

the counter's blind spot
input:  (  [  )  ]
round:  +1  0   0  -1   → final = 0, opens = closes — counter says VALID
square: 0  +1  0  -1   → final = 0, opens = closes — counter says VALID

But "([)]" is INVALID — the round ) closes before the square [ closes,
violating nesting order. A counter only knows counts, not order.

A stack knows order because it remembers what was most recently opened. When you see a closing bracket, check the stack top: if it matches, pop and continue. If it doesn't match or the stack is empty, the string is invalid immediately — no need to look further. At the end, if the stack is empty, every opener was closed in the right order.

the key invariant

At every closing bracket, the stack top must be the corresponding opener. This is the LIFO constraint rephrased: the most recently opened scope must be the first to close. This is precisely why a stack and not a counter, a queue, or a dictionary can validate nesting: only LIFO enforces "close in the reverse of open."

The rule also handles edge cases cleanly: stack empty when closing (extra closer), stack non-empty at end (unclosed opener). Both fall out of the same check without special cases.

This pattern extends to more complex nested structures: HTML/XML tag matching, matching function calls to returns in a call trace, validating that BEGIN/END blocks in a DSL are properly nested, or confirming that IF/ELSE/ENDIF in a bytecode instruction stream are well-formed.

§ 07 — expression evaluation and bracket validation

Widget C — the stack as a tiny processor

A stack evaluating a postfix expression is a minimal computer: read a token stream left to right, push operands, and when you hit an operator pop two operands, compute, push the result. The same visual object — a stack — also validates bracket nesting. Toggle between modes to see both use cases side-by-side.

Widget C · expression evaluator / bracket validator

step-through
token stream
Stack
Result
Select an expression or bracket sequence, then press Step.
number token operator opening bracket closing bracket (match) mismatch / error

Notice that the bracket validation mode uses the same stack object for a fundamentally different purpose: instead of accumulating numeric values, it accumulates "pending unmatched openers." The instruction "pop and verify on each closer" is structurally identical to "pop two operands on each operator." This is not a coincidence — both are consuming a token stream with the stack acting as working memory for the most recent context.

§ 08 — a boundary worth being precise about

Monotonic stack vs monotonic deque

Candidates consistently conflate these two because the mechanics look similar: in both cases you maintain a data structure in sorted order by evicting useless elements before pushing a new one. The difference is in the question being answered and the data structure required.

Monotonic stackProcesses the entire array once, left to right, and for each element produces a single answer ("what is my next greater element"). Elements are only evicted when a strictly better candidate arrives, and they're evicted from the top. No element ever needs to be evicted from the bottom because processed elements don't "expire" — once you've answered all elements smaller than a new arrival, they're done forever. A plain stack suffices: LIFO eviction is the only kind needed.
Monotonic dequeUsed in Sliding Window Maximum (and similar): as a window slides rightward, elements that fall outside the left edge of the window must be evicted regardless of their value — they expired by position, not by being beaten. That requires eviction from the front of the structure. You still evict useless smaller elements from the back (same as a stack). But the front-eviction requirement makes a deque (double-ended queue) necessary, not a plain stack.
the precise distinguishing question

Ask: "can elements ever become invalid by position (age out), or only by value (get beaten by a newcomer)?" If only by value → monotonic stack. If by position too → monotonic deque. Sliding Window Maximum is a deque problem because a previous maximum that's now outside the window is wrong even if it was the largest value ever seen. Daily Temperatures is a stack problem: a day's answer never expires; once answered, it's answered forever.

Practically, this means: if you see "maximum/minimum in a fixed-size sliding window," you need a deque. If you see "next/previous greater/smaller over the whole array," you need a stack. The Sliding Window topic covers the deque version in full; this topic owns the stack version. Do not conflate them under pressure — they are different algorithms serving different queries, and the interviewer will notice if you reach for a deque when a stack suffices or vice versa.

§ 09 — pattern recognition under time pressure

Recognition triggers — the checklist

Before spending 30 seconds trying to "figure out" a problem, run this list in 10 seconds. One hit is enough to start sketching.

reach for a stack when the problem has
  • Nested or matched structure — brackets, tags, nested function calls, BEGIN/END blocks, XML, HTML, any "what opened most recently must close first" constraint.
  • "Next / previous greater / smaller element" phrasing in any disguise — "how many days until a warmer temperature," "find the buildings visible from the ocean," "daily stock span," "how many days was the temperature the highest so far." All are next/previous greater/smaller element in costume.
  • Expression or formula evaluation — postfix/RPN, infix with operator precedence, calculator-style problems where operators apply to the two most recently seen operands.
  • "Undo the most recent operation" semantics — any problem where the most recent action is what you need to reverse or revisit. Backspace string compare, browser history, redo/undo in a text editor.
  • Greedy construction via a stack — "remove k digits to make the smallest number," "remove duplicate letters to get the lexicographically smallest result." The stack acts as a buffer whose top is evicted if a better (smaller) candidate arrives — the same monotonic pop condition, applied to string construction instead of query answering.
  • Area under a histogram — whenever a problem mentions bars, heights, buildings, or elevation profiles asking for area, span, or visibility, suspect a monotonic stack.
reach for a queue when the problem has
  • Level-order or BFS traversal — process all nodes at depth d before depth d+1. The queue is the scheduling structure.
  • "Process in arrival order" — tasks, requests, events that must be handled FIFO. Design problems for message queues, schedulers, printers.
  • "Implement one data structure using the other" — a design test of whether you understand LIFO vs FIFO at a mechanical level, not pattern matching.
§ 10 — beyond the interview

The same object in real systems

A small stack-based virtual machine used to validate spending conditions in a ledger system is, mechanically, the exact same object as the postfix-expression-evaluator interview pattern: an instruction stream is processed left to right, values are pushed, operators pop their operands and push a result, and the final state of the stack determines the outcome — here, transaction validity instead of a numeric answer. The interview pattern is not a toy abstraction; it is the production architecture.

Implementing conditional control flow (IF/ELSE/ENDIF) inside such a VM requires the same "use a stack to remember where you are and to validate nesting is well-formed" discipline as bracket-matching validation — control flow blocks must close in the same order they opened, which is precisely the LIFO matching constraint that makes a counter insufficient and a stack necessary. The bracket-validation pattern you see in Section 6 of this topic is not a contrived problem; it is a real correctness concern in any bytecode interpreter.

More broadly: call stacks in real program execution are the literal origin of the word "stack" in computer science. When a function calls another, its local state is pushed onto the call stack; when it returns, its frame is popped. Stack overflow is a real error caused by pushing too many frames. The stack that saves and restores caller state is the exact same LIFO structure you're using in these problems — same invariant, same failure mode, different scale.

the unifying thread

Whenever execution reaches a sub-problem whose answer must be "brought back" to the outer context — a nested function call, a subexpression in parentheses, a nested bracket group, an IF block to be jumped over — a stack is what carries the outer context forward safely, then restores it on return. This is not three separate use cases; it is one discipline at three different scales.

§ 11 — calibrated easy → hard

Problem set — 10

Ordered by difficulty. The first two are anchors that establish the baseline. Problems 4–8 are where Tier-1 custom banks live. Problem 10 is a deliberate contrast case, not new content — its purpose is to sharpen the stack-vs-deque boundary one final time.

§ 12 — language-agnostic templates

Pseudocode

Runnable-logic-correct. The four patterns you'll reach for most often — copy the shape, not the syntax.

1. Balanced bracket validation (multi-type)

bracket validation · O(n)
function is_valid(s):
    stack = []
    match = {']': '[', ')': '(', '}': '{'}
    for ch in s:
        if ch in {'(', '[', '{'}:
            stack.push(ch)          # opening bracket: remember it
        else:                       # closing bracket
            if stack.empty() or stack.top() != match[ch]:
                return False        # no matching opener, or wrong type
            stack.pop()             # matched — consume the opener
    return stack.empty()            # true iff every opener was closed

2. Monotonic stack — next greater element

next greater element · O(n)
function next_greater(arr):
    n = length(arr)
    result = [-1] * n               # default: no NGE found
    stack = []                      # stack of indices (not values)
    for i in 0..n-1:
        # Pop all indices whose NGE is arr[i]
        while stack not empty and arr[stack.top()] < arr[i]:
            result[stack.pop()] = arr[i]
        stack.push(i)
    # Elements remaining in stack have no NGE → result stays -1
    return result

# To find next SMALLER element: flip the comparison to arr[stack.top()] > arr[i]

3. Postfix (RPN) expression evaluation

postfix evaluation · O(n)
function eval_postfix(tokens):
    stack = []
    for tok in tokens:
        if tok is number:
            stack.push(to_int(tok))
        else:                       # tok is an operator (+, -, *, /)
            b = stack.pop()         # second operand (pushed later)
            a = stack.pop()         # first operand (pushed earlier)
            if   tok == '+': stack.push(a + b)
            elif tok == '-': stack.push(a - b)
            elif tok == '*': stack.push(a * b)
            elif tok == '/': stack.push(int(a / b))  # truncate toward zero
    return stack.pop()              # single remaining value is the answer

4. Queue using two stacks

queue via two stacks · amortized O(1) per operation
class MyQueue:
    inbox  = []    # push stack: receives all enqueues
    outbox = []    # pop stack: serves all dequeues

    function enqueue(val):
        inbox.push(val)              # O(1) always

    function dequeue():
        if outbox.empty():           # outbox empty: transfer all from inbox
            while inbox not empty:
                outbox.push(inbox.pop())
        return outbox.pop()          # O(1) amortized: each element moves once

    function peek():
        if outbox.empty():
            while inbox not empty:
                outbox.push(inbox.pop())
        return outbox.top()

# Key insight: reversing a stack by popping into another stack reverses insertion order,
# converting LIFO (stack) into FIFO (queue). Each element crosses inbox→outbox exactly once.
§ 13 — curated, not a dump

Further reading

Monotonic stack vs monotonic deque — the sharpest contrastThe Sliding Window topic in this series covers monotonic deque in full, but the contrast is most clearly stated in neetcode.io's "Monotonic Stack" video and the associated writeup on "Sliding Window Maximum" — watching both back-to-back with the question "what additional capability requires a deque?" is the fastest path to never confusing them again. The key sentence: a stack evicts only from the top; a deque evicts from both ends, which is required when elements must age out by position.
Stack-based virtual machines and bytecode interpretersThe CPython virtual machine (Python's default interpreter) is a stack machine: its main eval loop processes bytecode instructions that push operands and pop them for operators, exactly mirroring the postfix evaluator in Widget C. The CPython source at Python/ceval.c — specifically the _PyEval_EvalFrameDefault function — is a production-scale version of exactly the pattern you just stepped through. Forth, the JVM, and the WASM runtime are all stack machines for the same reason: simplicity of implementation without registers.
Real-world LIFO beyond interviewsCall stacks in program execution are the literal origin of the term "stack" in CS — the mechanism by which function calls save and restore caller context is LIFO, which is why stack overflow is a real error. Browser history (Back/Forward), undo/redo in text editors, and command-history in terminals (Ctrl+Z / Ctrl+Y) are all user-facing LIFO disciplines. For a system that makes LIFO explicit at the architecture level, Git's reflog is a practical stack of HEAD states you can step back through.
Competitive programming angle — when monotonic stack is non-obviousThe problems where monotonic stack is hardest to recognize are those where the "next greater element" relationship is implicit: Trapping Rain Water (the water level at each cell is bounded by min(maxLeft, maxRight), and those maxima can be tracked with a stack), Largest Rectangle in Histogram (the width of a rectangle centered at bar i extends to its nearest smaller bars on both sides), and "Number of Visible People in a Queue" (a building can see another only if no taller building intervenes — a sorted stack identifies these). Collecting these three as a set and tracing through each one with Widget A's visualization is a recommended warm-up before a competition.
How to use this: For every problem you attempt from the set, try to identify the trigger before looking at the sub-pattern label. The recognition skill — not the algorithm skeleton — is what separates candidates who ace this topic from those who stall on "disguised stack" problems. Widget A is reusable: come back and re-run it in NGE vs NSE mode on a fresh array of your choosing (edit the ARR variable in devtools) when the pattern fades.