Stacks & Queues Made Easy

Learn step-by-step with visual explanations and real-world analogies

Easy 20 min read Interactive

Why Should You Care About Stacks & Queues?

Stacks & queues power everyday technology

From your browser's back button to print jobs and game matchmaking, these two data structures are everywhere.

Browser Back Button

Your browser history is a stack! When you click back, you pop the current page and return to the previous one.

Undo/Redo Operations

Every Ctrl+Z (undo) pops an action from the stack. Ctrl+Y (redo) pushes it to another stack!

Call Center Queue

Customer service calls are handled in a queue - first caller gets helped first (FIFO).

Print Queue

Documents sent to a printer wait in a queue. First document sent prints first!

Game Matchmaking

Players waiting for a match are in a queue. First to join gets matched first!

App Navigation

Mobile app screens use a stack. Press back to pop the current screen!

The Basics: Start Here!

What is a Stack? Think of a Stack of Plates!

Imagine a stack of plates in a cafeteria. You can only take the top plate, and when you add a new plate, it goes on top. That's exactly how a stack works - Last In, First Out (LIFO)!

Stack = Pile of Plates
🥘
Top (Newest)
↑ Push / Pop ↓
🍲
Plate 3
🍛
Plate 2
🍕
Bottom (Oldest)

Stack Operations:

stack_basics.py
# Python's list works as a stack!
stack = []   # Empty stack (like empty plate holder)

# Push: Add plates to the top
stack.append("Plate 1")   # Stack: [Plate 1]
stack.append("Plate 2")   # Stack: [Plate 1, Plate 2]
stack.append("Plate 3")   # Stack: [Plate 1, Plate 2, Plate 3]

# Pop: Take the top plate
top_plate = stack.pop()   # Returns "Plate 3"
# Stack now: [Plate 1, Plate 2]

# Peek: Look at top without removing
if stack:   # Always check if not empty!
    current_top = stack[-1]   # "Plate 2"

# Check if empty
is_empty = len(stack) == 0

What is a Queue? Think of a Line at a Store!

A queue is like waiting in line at a grocery store. First person in line gets served first. That's First In, First Out (FIFO)!

Queue = Waiting in Line
👶
Rear (Newest)
👩‍👦
Person 3
👨‍💼
Person 2
👵
Front (Oldest)
→ Exit
queue_basics.py
from collections import deque

# Create a queue (deque is efficient for queues)
queue = deque()

# Enqueue: Join the line at the back
queue.append("Person 1")   # Queue: [Person 1]
queue.append("Person 2")   # Queue: [Person 1, Person 2]
queue.append("Person 3")   # Queue: [Person 1, Person 2, Person 3]

# Dequeue: Serve the first person
first = queue.popleft()   # Returns "Person 1"
# Queue now: [Person 2, Person 3]

# Peek at front without removing
if queue:
    front_person = queue[0]   # "Person 2"

Common Mistake: Stack Overflow

Each function call adds to the call stack. Without a base case, the stack grows until memory runs out!

stack_overflow.py
# ✘ WRONG - No size check!
def recursive_function(n):
    return recursive_function(n + 1)   # Infinite recursion!

# ✔ CORRECT - Always have a base case
def recursive_function(n):
    if n >= 100:   # Base case
        return n
    return recursive_function(n + 1)

Try It Yourself: Stack Operations

Interactive Stack

Common Patterns

Pattern 1: Balanced Parentheses (Stack)

Check if brackets are properly matched - a classic stack problem!

Opening bracket: Push to stack

Closing bracket: Pop and check if matches

End: Stack should be empty

balanced_parens.py
def is_balanced(s):
    stack = []
    pairs = {')': '(', ']': '[', '}': '{'}

    for char in s:
        if char in '([{':
            # Opening bracket - push to stack
            stack.append(char)
        elif char in ')]}':
            # Closing bracket - check if matches
            if not stack:
                return False   # No opening bracket
            if stack.pop() != pairs[char]:
                return False   # Mismatch

    return len(stack) == 0   # All matched if empty

# Examples
print(is_balanced("(())"))    # True
print(is_balanced("([)]"))    # False

Pattern 2: BFS with Queue

Breadth-First Search explores level by level using a queue!

level_order.py
from collections import deque

def level_order(root):
    if not root:
        return []

    result = []
    queue = deque([root])   # Start with root

    while queue:
        level_size = len(queue)
        current_level = []

        # Process all nodes at current level
        for _ in range(level_size):
            node = queue.popleft()   # Get from front
            current_level.append(node.val)

            # Add children to queue
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

        result.append(current_level)

    return result

Practice Problems

Easy Problem 1: Implement Min Stack

Design a stack that supports push, pop, and retrieving the minimum element in O(1) time.

What if you kept track of the minimum at each level?

class MinStack:
    def __init__(self):
        self.stack = []
        self.min_stack = []   # Track minimums

    def push(self, val):
        self.stack.append(val)
        # Push new min or current min
        if not self.min_stack or val <= self.min_stack[-1]:
            self.min_stack.append(val)
        else:
            self.min_stack.append(self.min_stack[-1])

    def pop(self):
        self.stack.pop()
        self.min_stack.pop()

    def get_min(self):
        return self.min_stack[-1]

Medium Problem 2: Implement Queue using Stacks

Create a queue using only two stacks.

One stack for input, one for output. Transfer when needed!

class MyQueue:
    def __init__(self):
        self.input_stack = []
        self.output_stack = []

    def enqueue(self, x):
        # Always push to input stack
        self.input_stack.append(x)

    def dequeue(self):
        # If output stack is empty, transfer all
        if not self.output_stack:
            while self.input_stack:
                self.output_stack.append(self.input_stack.pop())

        return self.output_stack.pop()

Advanced Techniques

Monotonic Stack

A stack where elements are always in increasing or decreasing order!

next_greater.py
def next_greater_element(nums):
    # Find next greater element for each number
    result = [-1] * len(nums)
    stack = []   # Monotonic decreasing stack

    for i, num in enumerate(nums):
        # Pop all smaller elements
        while stack and nums[stack[-1]] < num:
            idx = stack.pop()
            result[idx] = num   # Found greater element!

        stack.append(i)

    return result

# Example: [4, 5, 2, 10]
# Result:  [5, 10, 10, -1]

Circular Queue

A queue that wraps around - efficient use of fixed space!

circular_queue.py
class CircularQueue:
    def __init__(self, k):
        self.queue = [None] * k
        self.size = k
        self.front = 0
        self.rear = -1
        self.count = 0

    def enqueue(self, value):
        if self.count == self.size:
            return False   # Queue full

        self.rear = (self.rear + 1) % self.size
        self.queue[self.rear] = value
        self.count += 1
        return True

    def dequeue(self):
        if self.count == 0:
            return None   # Queue empty

        value = self.queue[self.front]
        self.front = (self.front + 1) % self.size
        self.count -= 1
        return value

Quick Reference

Operation Stack (LIFO) Queue (FIFO) Time
Add Element Push (top) Enqueue (rear) O(1)
Remove Element Pop (top) Dequeue (front) O(1)
View Next Peek/Top Front O(1)
Check Empty isEmpty isEmpty O(1)

When to Use Stack

Matching parentheses, function calls (recursion), undo operations, expression evaluation, backtracking algorithms.

When to Use Queue

BFS traversal, task scheduling, message passing, handling requests, level-order traversal.

Common Patterns

Monotonic Stack (next greater/smaller), Min/Max Stack (track extremes), Double-ended Queue (sliding window), Priority Queue (heap).