Less is more
A stack and a queue are restricted collections: they deliberately forbid random access and offer just two or three operations. The restriction is the feature — it makes behavior predictable, implementations trivially fast, and intent obvious in code.
| Stack | Queue | |
|---|---|---|
| Discipline | LIFO — Last In, First Out | FIFO — First In, First Out |
| Analogy | Plates: add and take from the top | A line at a counter: join at the back, served from the front |
| Operations | push, pop, peek | enqueue, dequeue, peek |
| All operations | O(1) | O(1) |
| Mental model | "most recent first" — undo, backtrack | "fairness" — process in arrival order |
Stacks
# Python — a list IS a stack (append/pop at the end are O(1))
stack = []
stack.append(10) # push
stack.append(20)
top = stack[-1] # peek → 20
x = stack.pop() # pop → 20
// Java — use ArrayDeque, not the legacy Stack class
Deque<Integer> stack = new ArrayDeque<>();
stack.push(10);
int top = stack.peek();
int x = stack.pop();
// C++
std::stack<int> st;
st.push(10);
int top = st.top();
st.pop(); // note: pop() returns void in C++
Where stacks already run your life
- The call stack — you met it in Functions: every call pushes a frame, every return pops one. Recursion (Level 3) is a stack, which is why any recursive algorithm can be rewritten with an explicit one.
- Undo / redo — every action pushes onto the undo stack; Ctrl-Z pops it (and pushes onto redo).
- Browser back button — pages visited, most recent first.
- Parsing — matching brackets, evaluating expressions, your compiler.
The interview classic: matched brackets
"Is {[()]} valid?" — opens push, closes must match the top:
def is_valid(s):
pairs = {")": "(", "]": "[", "}": "{"}
stack = []
for ch in s:
if ch in "([{":
stack.append(ch)
elif not stack or stack.pop() != pairs[ch]:
return False
return not stack # leftovers = unclosed opens
Why a stack? Because the most recently opened bracket must close first — the problem statement contains the letters L-I-F-O if you squint. Training yourself to hear "most recent first" → stack is the actual lesson.
One level up sits the monotonic stack — keep the stack sorted by popping everything your new element beats. It turns "next greater element" and "daily temperatures" style problems from O(n²) scans into one O(n) pass. Meet it properly in the problem bank.
Watch a stack work
Run a push/pop sequence — note who leaves first. Then edit the operations and try to predict the final state before pressing play.
1/11Empty stack. push adds to the top; pop removes from the top (LIFO).
Queues
# Python — NOT a list! Use deque (list.pop(0) shifts everything: O(n))
from collections import deque
q = deque()
q.append(10) # enqueue at the back
q.append(20)
front = q[0] # peek → 10
x = q.popleft() # dequeue from the front → 10, in O(1)
// Java
Queue<Integer> q = new ArrayDeque<>();
q.offer(10);
int x = q.poll();
// C++
std::queue<int> q;
q.push(10);
int x = q.front(); q.pop();
Where queues run the world
- BFS — breadth-first search: explore a graph level by level; the queue holds the frontier. This is the algorithmic reason queues exist, and it's how shortest paths in unweighted graphs are found.
- Task/message queues: printers, web servers under load, and the
industrial-strength version — Kafka/SQS pipelines
(messaging & queues) — are all "absorb
requests at the back, process from the front." The humble
dequehere and Level 6's distributed queue are the same idea at different scales. - Rate limiting & buffering: smooth a bursty producer feeding a steady consumer (rate limiting).
The deque — both ends at once
A deque (double-ended queue, "deck") allows O(1) push/pop at both ends — it's the general tool that plays stack and queue. Its star solo is the sliding-window maximum problem, where it powers an O(n) solution (sliding window).
Watch a queue work
Same operations, opposite exit door — the oldest element leaves first.
1/11Empty queue. enqueue adds at the back; dequeue removes from the front (FIFO).
Build one from the other (interview favorite)
Queue from two stacks: push into inbox; to dequeue, if outbox is
empty, pour inbox into it (reversing order), then pop outbox:
class QueueFromStacks:
def __init__(self):
self.inbox, self.outbox = [], []
def enqueue(self, x):
self.inbox.append(x)
def dequeue(self):
if not self.outbox:
while self.inbox:
self.outbox.append(self.inbox.pop()) # reverse the order
return self.outbox.pop()
Each element is moved at most twice ever → amortized O(1) per operation (the arrays page accounting again). The mirror problem — stack from queues — exists too; one of the pair shows up constantly in first rounds.
Production perspective
- Stacks and queues are usually built on arrays or linked lists — they're disciplines, not layouts. Array-backed (with the resize-doubling trick) is the practical default.
- The names persist up the stack: "stack trace," "task queue," "event queue" (the heart of Node.js — Level 7), "undo stack." When you read infrastructure docs, these aren't metaphors — there is literally a queue in there.
- Choosing LIFO vs FIFO is a product decision sometimes: a job scheduler that LIFOs feels snappy for new requests but starves old ones — fairness vs recency, in one letter.
Common mistakes
list.pop(0)as dequeue in Python — shifts every element, O(n); a loop of them is O(n²).deque.popleft()exists for exactly this.- Peeking/popping an empty stack — crash
(exceptions). The
not stackguard inis_validabove is half the solution's correctness. - Using Java's
Stackclass — legacy, synchronized, slow;ArrayDequeis the documented replacement. - Forgetting the leftover check — bracket-matching that returns True
with
"((("unprocessed. The finalreturn not stackmatters. - Hearing "queue" and reaching for sorting — FIFO already is the order; if you need priority order instead, that's a heap, two pages from now.
Think it through
The page teased the monotonic stack — now derive it. This is the canonical "turn an O(n²) re-scan into one O(n) pass with a stack" reasoning, and once it clicks you own a whole family of problems.
PROBLEMtemps = [73,74,75,71,69,72,76,73]. For each day, how many days until a WARMER temperature? Use 0 if none ever comes. Answer: [1,1,4,2,1,1,0,0].
- 1
Restate & edges
“For day i, what exactly am I looking for?”
- 2
Brute force first
“Obvious solution and its cost?”
unlocks after the stage above - 3
Find the pattern
“As I walk left to right, which past days are still 'waiting', and who resolves them?”
unlocks after the stage above - 4
Code the template
“Push indices (not temps) — why indices?”
unlocks after the stage above - 5
Cost & edge check
“Why is this O(n) even with a while-loop inside the for-loop?”
unlocks after the stage above
Check yourself
1. In bracket matching, which input is the reason you must check `not stack` BEFORE popping?
2. Why is `list.pop(0)` the wrong way to dequeue in Python?
3. BFS uses a queue, DFS uses a stack. Swap the container in the same traversal loop and you get:
4. Queue-from-two-stacks: one dequeue can cost O(n), yet we call it amortized O(1). Why?
Interview perspective
Practice — climb the ladder
If the problem whispers "most recent first" it is a stack; "first come, first served" is a queue; "next greater/smaller" is a monotonic stack.
Climb in order — every rung assumes the one above it. Solve on LeetCode, then tick it here; progress is saved on this device.
Warm-up
LIFO/FIFO mechanicsThe canonical stack problem — nesting means LIFO, with three distinct failure modes.
Two stacks make a queue — amortized analysis you can explain out loud.
Core
stacks carrying extra state- Min StackMedium
Auxiliary stack tracking the running minimum — state rides along with each push.
- Daily TemperaturesMedium
Monotonic stack — indices wait on the stack until a warmer day pops them.
Monotonic stack + hash map — the same pattern in its simplest costume.
Stack as evaluator — how compilers and calculators actually run expressions.
Stretch
the two famous hard onesMonotonic stack at full power — each bar pops exactly once; O(n) where brute force is O(n²).
Monotonic DEQUE — front holds the answer, back evicts the obsolete.