foreach-ui logo
codeLanguages
account_treeDSA

Quick Actions

quizlock Random Quiz
trending_uplock Progress
  • 1
  • 2
  • 3
  • quiz
Data Structures
  • Understand LIFO (Last In, First Out) principle with push/pop in O(1)
  • Implement stacks with arrays or linked lists
  • Apply stacks to undo/redo, balanced parentheses, DFS, expression evaluation

Introduction to Stacks

You're writing a text editor. Users type, make mistakes, hit undo. Each undo reverses the last action.

How do you track actions? An array works, but removing from the end is clumsy. You need the most recent action first.

Enter stacks. Last In, First Out (LIFO). Add actions to the top. Undo pops from the top. Simple.

What's a Stack?

A stack is like a stack of plates. You add plates on top. You take plates from the top.

LIFO: Last thing you put in is the first thing you take out.

stack = []

stack.append('Action 1')  # Push
stack.append('Action 2')
stack.append('Action 3')

# Stack now: ['Action 1', 'Action 2', 'Action 3']
# Top is Action 3

action = stack.pop()  # Pop from top
print(action)  # 'Action 3'

# Stack now: ['Action 1', 'Action 2']

Two operations:

  • push(x): Add x to top
  • pop(): Remove and return top element

Both O(1).

Implementation: Array

Use a dynamic array. Top is the last element.

class Stack:
    def __init__(self):
        self.items = []
    
    def push(self, item):
        self.items.append(item)
    
    def pop(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        return self.items.pop()
    
    def peek(self):
        if self.is_empty():
            return None
        return self.items[-1]
    
    def is_empty(self):
        return len(self.items) == 0
    
    def size(self):
        return len(self.items)

peek() looks at top without removing. Useful for checking what's next.

Implementation: Linked List

Use a singly linked list. Top is the head.

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class Stack:
    def __init__(self):
        self.top = None
        self.size = 0
    
    def push(self, item):
        new_node = Node(item)
        new_node.next = self.top
        self.top = new_node
        self.size += 1
    
    def pop(self):
        if self.is_empty():
            raise Exception("Stack is empty")
        data = self.top.data
        self.top = self.top.next
        self.size -= 1
        return data
    
    def peek(self):
        if self.is_empty():
            return None
        return self.top.data
    
    def is_empty(self):
        return self.top is None

Push: create node, point it to current top, make it new top. O(1).

Pop: store top's data, move top to next node. O(1).

Why Stacks?

Undo/Redo: Each action is pushed. Undo pops. Redo uses another stack.

undo_stack = Stack()
redo_stack = Stack()

def do_action(action):
    execute(action)
    undo_stack.push(action)
    redo_stack = Stack()  # Clear redo

def undo():
    if not undo_stack.is_empty():
        action = undo_stack.pop()
        reverse(action)
        redo_stack.push(action)

def redo():
    if not redo_stack.is_empty():
        action = redo_stack.pop()
        execute(action)
        undo_stack.push(action)

Function calls: Call stack. When you call a function, it's pushed. When it returns, it's popped.

def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n - 1)

# Call factorial(3)
# Stack: [factorial(3)]
# Calls factorial(2)
# Stack: [factorial(3), factorial(2)]
# Calls factorial(1)
# Stack: [factorial(3), factorial(2), factorial(1)]
# Calls factorial(0)
# Stack: [factorial(3), factorial(2), factorial(1), factorial(0)]
# Returns 1, pops factorial(0)
# Stack: [factorial(3), factorial(2), factorial(1)]
# Returns 1 * 1, pops factorial(1)
# Stack: [factorial(3), factorial(2)]
# Returns 2 * 1, pops factorial(2)
# Stack: [factorial(3)]
# Returns 3 * 2, pops factorial(3)
# Stack: []
# Final: 6

Balanced parentheses: Check if (), {}, [] are balanced.

def is_balanced(s):
    stack = Stack()
    pairs = {'(': ')', '{': '}', '[': ']'}
    
    for char in s:
        if char in pairs:
            stack.push(char)
        elif char in pairs.values():
            if stack.is_empty():
                return False
            if pairs[stack.pop()] != char:
                return False
    
    return stack.is_empty()

print(is_balanced("(){}[]"))     # True
print(is_balanced("({[]})"))     # True
print(is_balanced("({[})"))      # False
print(is_balanced("((()))"))     # True
print(is_balanced("(()"))        # False

Push opening brackets. When you see closing, pop and check match. If stack is empty at end, balanced.

Reversing

Stacks reverse order.

def reverse_string(s):
    stack = Stack()
    for char in s:
        stack.push(char)
    
    result = ""
    while not stack.is_empty():
        result += stack.pop()
    
    return result

print(reverse_string("hello"))  # "olleh"

Push all chars. Pop all chars. LIFO gives reverse order.

Depth-First Search (DFS)

Explore a graph depth-first using a stack.

def dfs(graph, start):
    visited = set()
    stack = Stack()
    stack.push(start)
    
    while not stack.is_empty():
        node = stack.pop()
        
        if node not in visited:
            print(node)
            visited.add(node)
            
            for neighbor in reversed(graph[node]):
                if neighbor not in visited:
                    stack.push(neighbor)

graph = {
    'A': ['B', 'C'],
    'B': ['D', 'E'],
    'C': ['F'],
    'D': [],
    'E': [],
    'F': []
}

dfs(graph, 'A')  # A, C, F, B, E, D (or similar order)

Iterative DFS with a stack instead of recursion.

Expression Evaluation

Evaluate postfix expressions (Reverse Polish Notation).

Infix: 3 + 4 * 2
Postfix: 3 4 2 * +
def eval_postfix(expr):
    stack = Stack()
    tokens = expr.split()
    
    for token in tokens:
        if token.isdigit():
            stack.push(int(token))
        else:
            b = stack.pop()
            a = stack.pop()
            
            if token == '+':
                stack.push(a + b)
            elif token == '-':
                stack.push(a - b)
            elif token == '*':
                stack.push(a * b)
            elif token == '/':
                stack.push(a // b)
    
    return stack.pop()

print(eval_postfix("3 4 2 * +"))  # 11
print(eval_postfix("5 1 2 + 4 * + 3 -"))  # 14

Push operands. When you see operator, pop two operands, apply operator, push result.

When to Use Stacks

Need to reverse order: Stacks naturally reverse.

Nested structures: Parentheses, HTML tags, function calls.

Backtracking: Explore one path, backtrack to try another. Stack tracks choices.

Undo functionality: Store actions, pop to undo.

Arrays vs Linked Lists for Stacks

Array:

  • Simple, fast access
  • Might need resizing (occasional O(n) when full)
  • Better cache locality

Linked List:

  • No resizing needed
  • Slightly more memory (pointers)
  • Constant O(1) always

For most cases, arrays work great. Python's list uses dynamic arrays with good amortization.

Common Mistakes

Popping empty stack: Always check is_empty() before pop().

# Wrong
result = stack.pop()  # Might crash if empty

# Right
if not stack.is_empty():
    result = stack.pop()

Forgetting peek vs pop: peek() doesn't remove. pop() does.

The Beauty

Stacks are simple. Push and pop. LIFO.

But they solve real problems:

  • Undo/redo
  • Function call management
  • Expression parsing
  • Depth-first search
  • Backtracking

Understanding stacks means understanding when you need to reverse order or track nested structures.

Master stacks, and you'll recognize LIFO patterns everywhere.

© 2026 forEach. All rights reserved.

Privacy Policy•Terms of Service