DSA Notes
A thorough introduction to stacks — the LIFO (Last In, First Out) data structure. Learn the stack model, all operations with complexity, real-world applications, stack implementations using arrays and linked lists, and the fundamental programming constructs that depend on stacks.
Introduction
A stack is a linear data structure that follows a strict LIFO (Last In, First Out) ordering rule — the last element added is the first one removed.
Think of a physical stack of plates: you always add plates to the top, and you always take them from the top. You cannot take a plate from the middle without removing all the plates above it first. The plate you placed most recently is the one you can remove right now.
Despite this apparent simplicity, stacks are one of the most powerful and widely used data structures in computer science. The call stack in every programming language, undo/redo systems, expression evaluation, browser history, backtracking algorithms — all depend on stacks.
Stack Operations
Core Operations
| Operation | Description | Time | Space |
|---|---|---|---|
push(x) | Add element x to the top | O(1) | O(1) |
pop() | Remove and return top element | O(1) | O(1) |
peek() / top() | Return top element without removing | O(1) | O(1) |
is_empty() | Return True if stack has no elements | O(1) | O(1) |
size() | Return number of elements | O(1) | O(1) |
All core operations are O(1) — this is what makes stacks efficient.
Implementation 1 — Stack Using Array (Python List)
Testing:
Implementation 2 — Stack Using Linked List
class _StackNode:
def __init__(self, data):
self.data = data
self.next = None
class LinkedStack:
"""
Stack using singly linked list.
Head of list = top of stack → O(1) push and pop.
"""
def __init__(self):
self._top = None # Head of list = top of stack
self._size = 0
def push(self, item):
"""Insert at head = push to top. O(1)."""
new_node = _StackNode(item)
new_node.next = self._top # New node points to old top
self._top = new_node # New top is the new node
self._size += 1
def pop(self):
"""Remove from head = pop from top. O(1)."""
if self.is_empty():
raise IndexError("pop from empty stack")
removed = self._top.data
self._top = self._top.next # Advance top to next node
self._size -= 1
return removed
def peek(self):
"""Look at head = peek at top. O(1)."""
if self.is_empty():
raise IndexError("peek from empty stack")
return self._top.data
def is_empty(self):
return self._top is None
def size(self):
return self._sizeWhy List-Based Stack is Preferred
| Feature | Array-Based | Linked List-Based |
|---|---|---|
| Push | O(1) amortized | O(1) always |
| Pop | O(1) | O(1) |
| Peek | O(1) | O(1) |
| Memory per element | 1 data slot | 1 data + 1 pointer |
| Memory growth | Doubling (wastes some) | Exact (one node per element) |
| Cache performance | Excellent | Poor |
| Simpler code | Yes | No |
Verdict: For most purposes, the array-based implementation (Python list) is preferred because it is simpler, faster in practice (cache friendly), and Python's list handles resizing automatically.
Real-World Applications
1. Function Call Stack (Most Important)
Every time you call a function, the programming language runtime pushes a stack frame onto the call stack containing:
- The function's local variables
- The return address (where to go when the function returns)
- The function's parameters
Stack overflow occurs when too many nested function calls fill the call stack. Python's default limit is ~1000 recursive calls.
2. Undo/Redo
Text editors and design tools use stacks for undo/redo:
3. Browser Back/Forward
class Browser:
def __init__(self):
self.back_stack = Stack() # Pages visited before current
self.forward_stack = Stack() # Pages visited after (when user went back)
self.current = None
def visit(self, url):
if self.current:
self.back_stack.push(self.current)
self.current = url
self.forward_stack = Stack() # Clear forward on new visit
def back(self):
if not self.back_stack.is_empty():
self.forward_stack.push(self.current)
self.current = self.back_stack.pop()
return self.current
def forward(self):
if not self.forward_stack.is_empty():
self.back_stack.push(self.current)
self.current = self.forward_stack.pop()
return self.current4. Expression Evaluation
Stacks evaluate mathematical expressions:
def evaluate_postfix(expression):
"""
Evaluate a postfix (Reverse Polish Notation) expression.
Example: "3 4 + 5 *" = (3+4)*5 = 35
"""
stack = Stack()
for token in expression.split():
if token.lstrip('-').isdigit():
stack.push(int(token))
else:
b = stack.pop() # Second operand
a = stack.pop() # First operand
if token == '+': stack.push(a + b)
elif token == '-': stack.push(a - b)
elif token == '*': stack.push(a * b)
elif token == '/': stack.push(int(a / b))
return stack.pop()
print(evaluate_postfix("3 4 + 5 *")) # 35
print(evaluate_postfix("2 3 1 * + 9 -")) # -45. Depth-First Search (DFS)
Python's Built-in Stack Support
Python's list has O(1) push and pop from the end — making it a natural stack:
Summary
The stack is the LIFO data structure:
- Push: Add to top → O(1)
- Pop: Remove from top → O(1)
- Peek: View top without removing → O(1)
- All operations are O(1) — stacks are extremely efficient
Real-world uses:
- Function call stack (every program)
- Undo/redo (every application)
- Browser back button
- Expression evaluation
- DFS traversal
- Backtracking algorithms
Implementations:
- Array-based (preferred): uses Python list, O(1) amortized
- Linked list-based: uses linked list head as top, O(1) always
*Next Lesson: Stack Implementation — Complete implementation with all edge cases, both array and linked list versions, and a fixed-size array stack.*
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Stack — Complete Introduction to LIFO Data Structure.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Data Structures & Algorithms topic.
Search Terms
data-structures-algorithms, data structures & algorithms, data, structures, algorithms, stacks, stack, introduction
Related Data Structures & Algorithms Topics