Data StructureCore ConceptHigh Frequency

Stacks & Queues

Two sides of the same coin. A stack processes the most recent item first (LIFO). A queue processes the earliest item first (FIFO). Together they are the backbone of recursion, BFS, undo systems, and dozens of classic interview problems.

Stack Analogy

A stack of plates at a buffet. You can only take the top plate, and you can only add a new plate on top. The last plate placed is the first one taken. That is LIFO: Last In, First Out.

Queue Analogy

A line at a coffee shop. The first person in line is the first person served. New customers join at the back, orders are filled from the front. That is FIFO: First In, First Out.

What Are Stacks & Queues? — Start Here if You're New

Stacks and queues are two of the simplest yet most powerful data structures in all of computer science. They both hold a collection of items, but they differ in the order you access those items. Let's break it down.

Stack: Like a Stack of Plates

You walk up to a buffet and see a spring-loaded plate dispenser. You can only take the plate on top, and when new plates are added, they go on top of the existing ones. The last plate placed is the first one you take. This is called LIFO — Last In, First Out.

Your computer uses a stack every time you call a function. Each function call gets “pushed” onto the call stack, and when a function finishes, it gets “popped” off. This is why infinite recursion causes a “stack overflow” — the stack runs out of room.

Queue: Like a Line at a Store

Imagine standing in line at a grocery store checkout. The first person in line is the first person served. New people join at the back, and people leave from the front. This is FIFO — First In, First Out.

Queues are everywhere: print job queues, network request queues, message queues in distributed systems, and most importantly, Breadth-First Search (BFS) in trees and graphs.

How They Work in Memory

Both stacks and queues can be built on top of arrays or linked lists. The underlying storage is just a container — what makes them special is the restricted access pattern:

  • Stack (array-backed): Uses a dynamic array internally. push() appends to the end and pop() removes from the end. Both are O(1). You never access the middle.
  • Queue (linked list or deque): Needs efficient removal from the front AND addition at the back. A doubly-linked list or deque handles both in O(1). Using a regular array for a queue is inefficient because removing from the front shifts all elements.

Key Takeaway for Beginners

Stacks and queues are not new data structures — they are access patterns built on top of arrays or linked lists. A stack says “you can only touch the top.” A queue says “add at the back, remove from the front.” These simple rules enable surprisingly powerful algorithms.

See It in Action

Click the buttons below to watch how each structure handles data. Notice the difference: the stack adds and removes from the same end, while the queue adds at one end and removes from the other.

Stack — LIFO

Like a stack of plates. Push adds to the top, pop removes from the top.

ABTOPpushpop

2 items — top is "B"

Queue — FIFO

Like a line at a coffee shop. Enqueue joins the back, dequeue leaves from the front.

P1FRONTP2BACKdequeueenqueue

2 in line — front is "P1", back is "P2"

Detailed Operation Walkthrough

Step through each operation frame by frame. Use the play button or navigate manually to see exactly what happens at each stage.

1 / 6
ABTOP → B

Stack contains: [A, B] (B is on top)

stack = [A, B], size = 2, top = B

How It Works — Visual Deep Dive

Understanding the mechanics is more important than memorizing syntax. Here is exactly what happens inside each data structure, step by step.

Stack Operations — Step by Step

1

push(val)

BC

New element lands on top. O(1).

2

pop()

BC

Top element removed and returned. O(1).

3

peek()

BC

Look at top element without removing. O(1).

Queue Operations — Step by Step

1

enqueue(val)

ABCFRONTBACK

New element joins the back. O(1).

2

dequeue()

ABCremoved

Front element removed and returned. O(1).

3

peek()

ABC

Look at front element without removing. O(1).

Real-World Applications

Stacks and queues are not abstract constructs. They power systems you use every day. Understanding these real-world applications helps you recognize which structure to reach for in an interview.

The Call Stack

Every function call pushes a frame. Every return pops it. Watch factorial(4) build up, then unwind.

main()Winding up (pushing frames)

1 frame on the stack

Browser Back / Forward

Two stacks working together. Going back pushes the current page onto the forward stack and pops from the back stack.

Back Stack

github.com
google.com

Current Page

leetcode.com

Forward Stack

youtube.com

BFS Uses a Queue

Breadth-first search explores nodes level by level. The queue ensures we process nodes in the order they were discovered.

1234567
Queue:1
Visited:none
ProcessingIn QueueVisited
Stack

Undo / Redo

Text editors push each action onto an undo stack. Undoing pops from undo and pushes to the redo stack. Two stacks, perfectly synchronized.

Queue

Print Queue

Documents are printed in the order they were submitted. The printer processes the front of the queue while new jobs join the back. Classic FIFO.

Stack

Expression Evaluation

Compilers use stacks to parse and evaluate mathematical expressions. Operators and operands are pushed/popped according to precedence rules.

Queue

Task Scheduling

Operating systems use queues (round-robin, priority queues) to schedule CPU time for processes. Every process waits its turn.

Operations & Complexity

Stack

OperationTimeSpace
push (add to top)O(1)*O(1)
pop (remove from top)O(1)O(1)
peek / topO(1)O(1)
searchO(n)O(1)
isEmptyO(1)O(1)

Queue

OperationTimeSpace
enqueue (add to back)O(1)*O(1)
dequeue (remove from front)O(1)O(1)
peek / frontO(1)O(1)
searchO(n)O(1)
isEmptyO(1)O(1)

* Amortized O(1)

Array-based stacks achieve amortized O(1) push because the underlying array occasionally doubles in capacity. Linked-list-based implementations are O(1) in the strict worst case. For queues, Python's collections.deque uses a doubly-linked list of blocks, giving true O(1) for both enqueue and dequeue.

Why Is Everything O(1)?

Both structures maintain a pointer to the access point. No searching or scanning required.

Stack: top pointer

data[0]data[1]data[2]top

Queue: head + tail pointers

ABCheadtail

The pointer gives direct access. Push/pop/enqueue/dequeue never need to scan the entire structure.

Under the Hood

Array-Based vs. Linked-List-Based

Array-based stack/queue: Stores elements in a contiguous block of memory. Great cache locality, but the array may need to resize. For a stack this is trivial (push/pop at the end). For a queue, naive array-based implementations suffer O(n) dequeue because every element must shift forward — which is why you should use a circular buffer or a deque instead.

Linked-list-based: Each node points to the next. True O(1) insert/remove at both ends (with a tail pointer for queues). The trade-off is higher memory overhead per element and worse cache performance.

Deque (Double-Ended Queue)

A deque supports O(1) insertion and removal at both ends. It is a superset of both stack and queue. In Python, use collections.deque. In JavaScript, there is no built-in deque — arrays support push/pop in O(1) but shift/unshift are O(n). For O(1) queue operations in JS, use a linked list or an index-based approach (shown in the implementation section below).

Interview Patterns

These are the four patterns that cover the vast majority of stack and queue interview questions. Recognize the pattern and the solution writes itself.

Valid Parentheses (Matching Brackets)

The classic stack problem. Push every opening bracket. When you see a closing bracket, pop and check it matches. If the stack is empty at the end, the string is valid. This pattern extends to any problem involving nested or paired structures: HTML tags, mathematical expressions, directory paths.

Monotonic Stack (Next Greater Element)

Maintain a stack where elements are in decreasing (or increasing) order. For each new element, pop everything smaller — those popped elements have found their "next greater element." This runs in O(n) because each element is pushed and popped at most once. Use this for stock span, daily temperatures, largest rectangle in histogram.

BFS with Queue

Any time you see "shortest path in unweighted graph", "minimum number of steps", or "level-order traversal" — reach for BFS with a queue. Enqueue the start, process level by level, track visited nodes. The queue guarantees you explore in order of distance from the source.

Min Stack (getMin in O(1))

Design a stack that supports push, pop, top, and getMin — all in O(1). The trick: maintain a second stack (or store pairs) that tracks the current minimum at each level. When you push, also push the new min. When you pop, pop from both.

Common Mistakes to Avoid

  • Popping from an empty stack — always check isEmpty() before pop(). In interviews, forgetting this edge case is a red flag.
  • Confusing LIFO and FIFO — if you mix up which end you add to and remove from, your entire algorithm breaks. Stack: same end. Queue: opposite ends.
  • Using Array.shift() as a queue in JavaScript — shift() is O(n) because it re-indexes every element. Use an object-based queue or a linked list for O(1) dequeue.
  • Not considering deque when you need both stack and queue behavior — Python's collections.deque gives you O(1) append and popleft. Reach for it by default.
  • Forgetting to handle the final state of the stack — in problems like valid parentheses, you must check that the stack is empty at the end, not just that every closing bracket matched.

The Key Insight

If you need to process things in reverse order, think stack. If you need to process in order of arrival, think queue. Every stack/queue interview problem boils down to this distinction. Valid Parentheses? You need to match the most recent opening bracket — that is reverse order — use a stack. BFS? You need to explore the earliest discovered node — that is arrival order — use a queue.

Implementation

Now that you understand the concepts, here is the code. Every line is commented. Python and JavaScript implementations side by side.

Stack Class

The Idea

A stack is just an array where you only ever touch the last element. Push appends to the end, pop removes from the end, peek looks at the end. Because arrays support O(1) access to their last element, every stack operation is O(1).

Before you read the code, understand this:

  • Python's list.append() and list.pop() are both O(1) amortized — they operate on the end of the internal array.
  • Always check is_empty() before pop/peek to avoid runtime errors — interviewers watch for this.
Stack — from scratch
1class Stack:
2 """Stack implemented with a Python list.
3 push/pop/peek are all O(1) amortized."""
4
5 def __init__(self):
6 self._data = [] # internal storage
7
8 def push(self, val):
9 """Add element to the top of the stack."""
10 self._data.append(val) # list.append is O(1) amortized
11
12 def pop(self):
13 """Remove and return the top element."""
14 if self.is_empty():
15 raise IndexError("pop from empty stack")
16 return self._data.pop() # list.pop() removes last item — O(1)
17
18 def peek(self):
19 """Return the top element without removing it."""
20 if self.is_empty():
21 raise IndexError("peek from empty stack")
22 return self._data[-1] # negative index — O(1)
23
24 def is_empty(self):
25 return len(self._data) == 0
26
27 def size(self):
28 return len(self._data)

Dry Run — Stack operations

OperationInternal _dataReturnssize()
Stack()[]-0
push(10)[10]-1
push(20)[10, 20]-2
push(30)[10, 20, 30]-3
peek()[10, 20, 30]303
pop()[10, 20]302
pop()[10]201

Notice: 30 was pushed last but popped first — LIFO (Last In, First Out).

Key Line Spotlight

return self._data.pop() # list.pop() removes last item -- O(1)

Python's list.pop() without an argument removes the last element, which is O(1) because no shifting is needed. Contrast this with list.pop(0) which removes the first element and requires shifting everything — O(n). This is why stacks are backed by arrays but queues need a different approach.

Queue Class

The Idea

A queue needs O(1) operations at both ends: add to the back, remove from the front. A plain array would require O(n) shifting on dequeue. The fix: Python uses collections.deque (doubly-linked list of blocks), JavaScript uses an index-based approach with head/tail pointers.

Before you read the code, understand this:

  • Never use list.pop(0) for a queue in Python — it is O(n). Use deque.popleft() which is O(1).
  • Never use Array.shift() for a queue in JavaScript — it is O(n). The index-based approach avoids shifting entirely.
Queue — from scratch
1from collections import deque
2
3class Queue:
4 """Queue backed by collections.deque for O(1) operations.
5 deque uses a doubly-linked list of blocks internally."""
6
7 def __init__(self):
8 self._data = deque() # O(1) append and popleft
9
10 def enqueue(self, val):
11 """Add element to the back of the queue."""
12 self._data.append(val) # append to right end — O(1)
13
14 def dequeue(self):
15 """Remove and return the front element."""
16 if self.is_empty():
17 raise IndexError("dequeue from empty queue")
18 return self._data.popleft() # remove from left end — O(1)
19
20 def peek(self):
21 """Return front element without removing it."""
22 if self.is_empty():
23 raise IndexError("peek from empty queue")
24 return self._data[0] # direct access to front — O(1)
25
26 def is_empty(self):
27 return len(self._data) == 0
28
29 def size(self):
30 return len(self._data)

Dry Run — Queue operations

OperationInternal stateReturnssize()
Queue()deque([])-0
enqueue('A')deque(['A'])-1
enqueue('B')deque(['A','B'])-2
enqueue('C')deque(['A','B','C'])-3
peek()deque(['A','B','C'])'A'3
dequeue()deque(['B','C'])'A'2
dequeue()deque(['C'])'B'1

Notice: ‘A’ was enqueued first and dequeued first — FIFO (First In, First Out).

Key Line Spotlight

return self._data.popleft() # remove from left end -- O(1)

This is why deque exists. A regular Python list would need to shift all remaining elements left after removing index 0 — O(n) per dequeue. deque.popleft() is O(1) because deque is backed by a doubly-linked list of fixed-size blocks. In JavaScript, the index-based approach achieves the same O(1) by incrementing head instead of shifting.

Valid Parentheses

The Idea

Every opening bracket must be matched by the most recent unmatched closing bracket of the same type. “Most recent” screams stack — LIFO. Push every opening bracket. When you see a closing bracket, pop and check if it matches.

Before you read the code, understand this:

  • The matching dictionary maps each closing bracket to its expected opening bracket.
  • Three failure modes: (1) closing bracket with empty stack, (2) mismatched pair, (3) leftover opening brackets at the end.
LeetCode #20 — Valid Parentheses
1def is_valid(s: str) -> bool:
2 """LeetCode #20 — Valid Parentheses.
3 Use a stack to match every closing bracket
4 with the most recent unmatched opening bracket."""
5
6 stack = [] # our stack of opening brackets
7 matching = {')': '(', '}': '{', ']': '['}
8
9 for char in s:
10 if char in matching:
11 # It's a closing bracket — check the top of stack
12 if not stack or stack[-1] != matching[char]:
13 return False # mismatch or nothing to match
14 stack.pop() # matched — remove the opening bracket
15 else:
16 # It's an opening bracket — push it
17 stack.append(char)
18
19 return len(stack) == 0 # all brackets must be matched
20
21# is_valid("()[]{}") -> True
22# is_valid("(]") -> False
23# is_valid("{[]}") -> True

Dry Run — s = "{[]}()"

charTypeActionStack after
{OpenPush '{'['{']
[OpenPush '['['{', '[']
]ClosePop '[', matches ']'['{']
}ClosePop '{', matches '}'[]
(OpenPush '('['(']
)ClosePop '(', matches ')'[]

Stack is empty at the end → True. All brackets matched.

Key Line Spotlight

if not stack or stack[-1] != matching[char]: return False

This line handles two failure cases at once. First, not stack catches a closing bracket with no matching opener (stack underflow). Second, stack[-1] != matching[char] catches mismatched pairs like ({]. This combined check is the core logic — everything else is just scaffolding.

Why O(n) time, O(n) space?

  • The for loop iterates through each character once → O(n).
  • In the worst case (all opening brackets), the stack holds n/2 elements → O(n) space.

Monotonic Stack — Next Greater Element

The Idea

For each element, you want to find the next element to its right that is larger. Brute force checks every pair — O(n²). The monotonic stack trick: maintain a stack of “waiting” elements (in decreasing order). When a new element arrives that is larger than the stack top, that element is the answer for all smaller elements on the stack. Pop them and record the answer.

Before you read the code, understand this:

  • The stack stores indices (not values), so we can write answers to the correct positions.
  • Each element is pushed at most once and popped at most once → total work is O(n), not O(n²).
  • Elements left on the stack at the end have no next greater element — they stay as -1 (the default).
Next Greater Element for each position
1def next_greater_element(nums: list[int]) -> list[int]:
2 """For each element, find the next element that is strictly larger.
3 Returns -1 if no greater element exists to the right.
4
5 The stack maintains indices in monotonically decreasing order.
6 Each element is pushed and popped at most once — O(n) total."""
7
8 n = len(nums)
9 result = [-1] * n # default: no greater element
10 stack = [] # stack of indices (decreasing values)
11
12 for i in range(n):
13 # Pop all elements smaller than current — they found their answer
14 while stack and nums[stack[-1]] < nums[i]:
15 idx = stack.pop()
16 result[idx] = nums[i] # nums[i] is the next greater element
17 stack.append(i) # push current index
18
19 return result
20
21# next_greater_element([2, 1, 2, 4, 3]) -> [4, 2, 4, -1, -1]

Dry Run — nums = [2, 1, 2, 4, 3]

inums[i]Stack (indices)Actionresult
02[]Push 0[-1,-1,-1,-1,-1]
11[0]1<2, just push 1[-1,-1,-1,-1,-1]
22[0,1]2>1: pop 1, result[1]=2. Push 2[-1, 2,-1,-1,-1]
34[0,2]4>2: pop 2, result[2]=4. 4>2: pop 0, result[0]=4. Push 3[ 4, 2, 4,-1,-1]
43[3]3<4, just push 4[ 4, 2, 4,-1,-1]

Final: [4, 2, 4, -1, -1]. Indices 3 and 4 remain on the stack with no greater element.

Key Line Spotlight

while stack and nums[stack[-1]] < nums[i]:

This while loop is where the magic happens. It pops every “waiting” index whose value is smaller than the current element — because the current element is their next greater element. Despite being a nested loop, each element is pushed and popped at most once across the entire algorithm, so the total work is O(n). This amortized argument is exactly what interviewers want you to articulate when they ask “isn't this O(n²)?”

Why O(n) time, O(n) space?

  • Each of the n elements is pushed onto the stack exactly once and popped at most once → O(n) total operations.
  • The stack and result array each hold at most n elements → O(n) space.
  • The key insight: the while loop does NOT make this O(n²). Total pops across all iterations of the for loop = n.

Practice Problems

Start from the top and work your way down. Each problem reinforces a different aspect of stack and queue thinking.

Interviewer's Perspective

What Interviewers Are Really Testing

  • 1.Data structure selection. When do you reach for a stack vs a queue vs a deque? Matching parentheses = stack. BFS = queue. Sliding window max = deque. This decision reveals your depth of understanding.
  • 2.Monotonic stack intuition. “Next greater element” and “largest rectangle in histogram” are classic stack problems. Interviewers want to see you maintain the stack invariant correctly.
  • 3.Call stack understanding. Can you explain how recursion uses the call stack? Can you convert a recursive DFS to an iterative one using an explicit stack? This is tested at senior levels.
  • 4.Design problems. “Implement a Min Stack” and “Design a Queue using Stacks” test whether you truly understand the mechanics, not just the API.

Related Patterns & Concepts

When to Use Stacks & Queues — Decision Guide

Use a Stack When...

  • You need to process items in reverse order (most recent first).
  • You see matching/nesting problems: parentheses, HTML tags, expression evaluation.
  • You need undo/redo functionality or backtracking (DFS).
  • You see “next greater element” or “stock span” — monotonic stack.

Use a Queue When...

  • You need to process items in arrival order (first come, first served).
  • You see shortest path in unweighted graph or “minimum steps” — BFS.
  • You need level-order traversal of a tree.
  • You are implementing a task scheduler, message broker, or producer-consumer system.