Data Structures & Algorithms · Data Structures & Algorithms
Stacks and Queues
LIFO stacks and FIFO queues with applications like expression parsing and monotonic stacks.
Eight concepts on LIFO stacks and FIFO queues — the core operations, parentheses matching, monotonic next-greater, a two-stack queue, deques for sliding-window maxima, and when each structure is the right tool.
- Data Structures & Algorithms
- Easy level
- 8 concepts
- 5 practice questions
1Stack: last in, first out
A stack is a LIFO structure: push and pop both act at one end, the top. Each of those operations is O(1). Stacks model nested work — recursion call frames, undo history, and the unmatched openers in an expression — because the most recently opened obligation is the one that must close first.
Figure. Stack after push 1, then 2, then 3. Only the top cell is reachable for the next pop.
How push and pop work
- PushPlace a new element on the top. The previous top becomes the second element.
- PopRemove and return the current top. The element under it is exposed.
- PeekRead the top without removing it — still constant time.
After push(1), push(2), push(3), a single pop returns
- 1 — the first value pushed
- 3 — the last value pushed
- 2 — the middle value
LIFO: the last push (3) is the first pop. 1 remains buried until 3 and 2 are removed.
2Queue: first in, first out
A queue is FIFO: enqueue at the back, dequeue at the front, each O(1) with a proper implementation. Queues order work by arrival — BFS level expansion, print jobs, request handlers — because the oldest waiting item is the next one served.
Figure. Queue after enqueue 1, then 2, then 3. Dequeue pulls from the left; enqueue appends on the right.
How enqueue and dequeue work
- EnqueueAttach the new element at the back. It waits behind everything already in line.
- DequeueRemove the front element — the one that has waited longest.
- Empty checkFront and back meet (or the structure reports empty) when nothing remains to serve.
After enqueue(1), enqueue(2), enqueue(3), a single dequeue returns
- 3 — the last value enqueued
- 1 — the first value enqueued
- 2 — the middle value
FIFO: the earliest arrival (1) leaves first. Stacks would have returned 3.
3Valid parentheses with a stack
Checking that brackets are balanced and correctly nested is the textbook stack problem. Scan left to right: push every opener; on a closer, pop and demand an exact match. Fail if the stack is empty on a closer, if a pop mismatches, or if anything remains when the string ends. Time and worst-case stack space are both O(n).
Figure. Scan left to right: push openers, pop on a matching closer; empty stack at end means valid nesting.
One left-to-right pass
- OpenerPush '(', '[', or '{' onto the stack.
- CloserPop; the opener must be the matching mate. Empty stack or wrong mate ⇒ invalid.
- End of stringValid only if the stack is empty — every opener found its closer.
Valid parentheses
def is_valid(s):
mate = {')': '(', ']': '[', '}': '{'}
stack = []
for ch in s:
if ch in '([{':
stack.append(ch)
elif not stack or stack.pop() != mate[ch]:
return False
return not stackCheck ([])
Is s = '([{}])' valid?
- see '('stack ['(']
- see '['stack ['(', '[']
- see '{', then '}'push '{'; pop matches '{'
- see ']', then ')'pop '[', then '(' — empty
- end of stringvalid
Pro tip. A stack captures nesting depth: the closer must match the most recent unmatched opener, not just any opener of the right type earlier in the string.
Coding lab. Check ([]) nested brackets runs in the app, with checks on your output.
The string '([)]' fails because
- It contains four brackets, and only even counts are allowed
- ']' tries to close '[' while '(' is not yet closed — the top of the stack is wrong
- Parentheses may never nest inside square brackets
After '([', the stack top is '['. The next character ')' needs '(', so the match fails even though every type appears once.
4Monotonic stack: next greater element
A monotonic stack stays strictly decreasing (or increasing) by value. For next-greater-element, store indices of values still waiting for a larger neighbour to their right. Scan left to right; while the current value beats the value at the stack top, pop and record the answer. Each index is pushed and popped at most once, so the whole pass is O(n) instead of a nested O(n^2) scan.
Figure. Array from the worked example. Annotations above each cell are that cell's next greater to the right.
Decreasing stack of indices
- Hold waitersStack stores indices whose next greater is still unknown, in decreasing value order.
- ResolveWhen nums[i] is greater than nums[stack.top], pop and set answer[popped] = nums[i].
- Push ii itself may still need a greater element further right.
- LeftoversIndices still on the stack at the end have no greater element — answer -1.
Next greater element
def next_greater(nums):
ans = [-1] * len(nums)
stack = []
for i, value in enumerate(nums):
while stack and nums[stack[-1]] < value:
ans[stack.pop()] = value
stack.append(i)
return ansNext greater of [2, 1, 2, 4, 3]
For nums = [2, 1, 2, 4, 3], find for each element the next greater value to its right (or -1).
- i=0 value 2stack [0]
- i=1 value 1 (< 2)stack [0, 1]
- i=2 value 2; pop 1ans[1]=2; stack [0, 2]
- i=3 value 4; pop 2,0ans[2]=4, ans[0]=4; stack [3]
- i=4 value 3; endans [4, 2, 4, -1, -1]
Pro tip. Daily-temperatures and next-smaller problems are the same pattern with the comparison flipped. If you reach for a nested loop, ask whether a monotonic stack collapses it to linear.
Why is next-greater with a monotonic stack O(n) total?
- Because binary search finds each answer in logarithmic time
- Because each index is pushed once and popped once
- Because the stack height is always at most three
Amortized accounting: n pushes and at most n pops across the whole scan, even though one iteration may pop several times.
5Queue from two stacks
When the only primitive is a stack, a queue is two stacks: in for enqueue, out for dequeue. Enqueue always pushes onto in. Dequeue pops from out; if out is empty, pour every element from in into out first (reversing order), then pop. Each element moves at most twice, so enqueue and dequeue are amortized O(1).
Figure. Enqueue on the in-stack; when the out-stack is empty, flush in→out so dequeue reads FIFO order.
Amortized handoff
- EnqueuePush onto in.
- Dequeue, out readyIf out is non-empty, pop out — that is the front.
- Dequeue, out emptyWhile in is non-empty, pop in and push onto out. Then pop out.
Enqueue 1,2 then dequeue
Start empty. Enqueue 1, enqueue 2, then dequeue once. What are the stacks after the dequeue?
- enqueue 1in=[1], out=[]
- enqueue 2in=[1,2], out=[]
- dequeue: pour in→outin=[], out=[2,1] (1 on top)
- pop outreturns 1; out=[2]
Pro tip. The pour looks O(n), but each element is poured at most once in its lifetime — charge that cost to the enqueue that introduced it, and the amortized cost per operation is constant.
In a two-stack queue, when do you move elements from in to out?
- After every enqueue
- Only when a dequeue finds out empty
- Never — both stacks stay independent forever
Pouring on every enqueue wastes work. Lazy transfer when out is empty preserves FIFO and keeps the amortized bound.
6Deque and sliding-window maximum
A deque supports insert and remove at both ends in O(1). For sliding-window maximum, keep a deque of indices whose values are decreasing: the front is always the max in the current window. Drop indices that left the window from the front; drop smaller candidates from the back before pushing the new index. One pass is O(n) time and O(k) deque space — better than a heap's O(n \log k).
Figure. First window of length 3 over [1, 3, −1, −3, 5]; its maximum is 3 at the second cell.
Monotonic deque
- Expire the frontWhile the front index is outside [i−k+1, i], pop left.
- Dominated backsWhile the back's value ≤ nums[i], pop right — it can never be max while i is in the window.
- Push and readPush i. Once the window is full, nums[front] is the answer for this i.
Window max on [1, 3, −1, −3, 5], k = 3
Compute the maximum in every contiguous window of length 3.
- window [1,3,-1]max 3
- window [3,-1,-3]max 3
- window [-1,-3,5]max 5
- answer list[3, 3, 5]
Pro tip. If the interviewer offers a heap, mention the deque: same problem, linear time, because each index enters and leaves the deque at most once.
Sliding-window maximum via a monotonic deque is preferred over a heap mainly because
- It uses O(n) time versus O(n \log k) for the heap
- It needs no array at all
- Heaps cannot store integers
Both can find window maxima; the deque's amortized one-enter-one-leave accounting removes the log factor.
7Stacks for expression parsing
Beyond matching brackets, stacks evaluate and convert expressions. Postfix (RPN) evaluation pushes numbers and, on an operator, pops operands, applies the operator, and pushes the result. Infix-to-postfix conversion uses an operator stack with precedence rules. Prefix walks right-to-left with the same idea. The shared habit: delayed operators wait on a stack until their operands are ready.
Figure. Postfix mid-state: numbers wait on the stack until an operator arrives. Seeing × pops 4 and 5, pushes 20.
Postfix evaluation
- NumberPush it on the value stack.
- OperatorPop right operand, pop left operand, apply, push the result.
- DoneOne value remains on the stack — the expression's value.
Evaluate 2 3 + 4 ×
Postfix tokens [2, 3, '+', 4, '×']. What is the value?
- push 2, push 3stack [2, 3]
- see '+': 2+3stack [5]
- push 4stack [5, 4]
- see '×': 5×4stack [20]
Pro tip. Operand order matters for non-commutative operators: pop right first, then left, so subtraction and division keep the written order.
In postfix evaluation, when you see an operator you
- Push the operator and continue
- Pop two operands, apply the operator, push the result
- Clear the stack and start over
Operators are never stored in the value stack for pure postfix evaluation — they trigger an immediate binary (or unary) reduce.
8When to reach for which
Pick the structure from the access pattern the problem needs. Nesting, undo, matching openers, next-greater, and expression operators want a stack. Arrival order, BFS, and fair scheduling want a queue. Both-ends work and sliding-window extrema want a deque. Implementing one from the other (queue from two stacks, stack from two queues) is an interview constraint trick, not a default design.
Figure. Stack for LIFO nesting and undo; queue for BFS layers; monotonic stack for next-greater patterns.
Decision cues
- Nesting or "most recent"Stack — parentheses, path undo, monotonic waiters.
- Oldest first or level orderQueue — BFS, task queues, printers.
- Both ends or window extremaDeque — sliding-window maximum, steque-style APIs.
| Structure | Discipline | Exam / interview cue |
|---|---|---|
| Stack | LIFO | Balanced brackets; next greater; undo; recursion |
| Queue | FIFO | BFS; scheduling; "process in arrival order" |
| Deque | Both ends | Sliding-window maximum; insert/remove front and back |
| Two stacks | Simulated FIFO | "Implement a queue using stacks" |
"Find the next warmer day for each day in a temperature list" is best approached with
- A FIFO queue of all days
- A monotonic stack of indices awaiting a warmer day
- A deque used only as a plain stack
Next-warmer is next-greater on a timeline — the monotonic stack pattern from earlier, not arrival-order FIFO.
Notes
- Stack (LIFO): Last-In-First-Out with push and pop at one end, each O(1); used for recursion, undo, and expression evaluation.
- Queue (FIFO): First-In-First-Out with enqueue at the back and dequeue at the front, each O(1); used for BFS and scheduling.
- Monotonic Stack: A stack kept in increasing or decreasing order, solving 'next greater/smaller element' problems in O(n).
- Expression Parsing: Stacks validate balanced parentheses and evaluate/convert infix, postfix, and prefix expressions.
- Deque and Variants: A double-ended queue supports insertion/removal at both ends and underlies sliding-window-maximum solutions; a circular queue reuses freed slots.
Formulas
- Push/pop and enqueue/dequeue: O(1).
- Monotonic stack over an array: O(n) total because each element is pushed and popped at most once.
- Two-stack queue: amortized O(1) per enqueue/dequeue.
- Balanced-parentheses check: O(n) time, O(n) worst-case stack space.
- Sliding window maximum via deque: O(n) time, O(k) space.
Exam traps & shortcuts
- For 'next greater element' or 'daily temperatures,' use a monotonic stack to hit O(n) instead of O(n^2).
- Any balanced-brackets or valid-expression question is a stack problem - push openers, pop and match on closers.
- Implement a queue with two stacks (or a stack with two queues) when the interviewer restricts the primitive.
- For sliding-window maximum, a monotonic deque beats a heap: O(n) versus O(n \log k).
Reference tables
Amortized bounds assume the standard accounting (each element enters and leaves a structure a constant number of times).
| Operation / algorithm | Time | Extra space |
|---|---|---|
| Push / pop / enqueue / dequeue | O(1) | O(1) |
| Balanced-parentheses check | O(n) | O(n) worst case |
| Monotonic next-greater | O(n) | O(n) |
| Two-stack queue (amortized) | O(1) per op | O(n) |
| Sliding-window maximum (deque) | O(n) | O(k) |
Recap
Stacks nest; queues line up; deques do both ends. Most interview wins here are recognizing which discipline the problem already is.
- Pick structure
- LIFO ⇒ stack; FIFO ⇒ queue; both ends / window max ⇒ deque.
- Brackets
- Brackets: push openers, pop on closers, empty at end.
- Next greater
- Next greater: decreasing index stack; each index once ⇒ O(n).
- Two-stack queue
- Two-stack queue: pour in→out only when out is empty (amortized O(1)).
- Window max
- Window max: monotonic deque beats a heap's O(n \log k).
Practise Stacks and Queues
Reading is free and needs no account. Practice, mocks and progress live in the app.
- 5 exam-style questions on this topic, with explanations
- A 5-question practice set that ends the chapter
- Timed mocks scored with the real marking scheme
- Readiness tracked per topic, kept on your device