Python Tutorial
Stacks
A Last-In-First-Out (LIFO) structure: the most recently added item is the first one removed.
The LIFO Principle
A stack works like a pile of plates: you add (push) to the top and remove (pop) from the top. The last item pushed is the first popped — Last In, First Out. All operations touch only the top, so they run in O(1).
Stacks power function-call management (the "call stack"), undo/redo, expression evaluation, backtracking, and depth-first search.
Stack with a Python List
stack = []
stack.append("A") # push
stack.append("B")
stack.append("C") # stack: ['A', 'B', 'C']
print(stack.pop()) # 'C' (removes top)
print(stack[-1]) # 'B' (peek without removing)
print(len(stack)) # 2
print(len(stack) == 0) # False -> not emptyA list's append and pop at the end are both O(1), making it a natural stack.
A Reusable Stack Class
class Stack:
def __init__(self):
self._items = []
def push(self, value):
self._items.append(value)
def pop(self):
if self.is_empty():
raise IndexError("pop from empty stack")
return self._items.pop()
def peek(self):
return self._items[-1]
def is_empty(self):
return len(self._items) == 0
def size(self):
return len(self._items)
s = Stack()
s.push(1); s.push(2)
print(s.pop(), s.peek(), s.size()) # 2 1 1Prefer deque for Heavy Use
collections.deque is thread-safe for appends/pops and never needs to resize-copy, so it is the recommended stack for performance-critical code.
from collections import deque
stack = deque()
stack.append(10)
stack.append(20)
print(stack.pop()) # 20Classic Application: Balanced Brackets
def is_balanced(text):
pairs = {")": "(", "]": "[", "}": "{"}
stack = []
for ch in text:
if ch in "([{":
stack.append(ch)
elif ch in ")]}":
if not stack or stack.pop() != pairs[ch]:
return False
return len(stack) == 0
print(is_balanced("(a[b]{c})")) # True
print(is_balanced("(a[b)]")) # FalseEach opening bracket is pushed; each closing bracket must match the most recent opening bracket — exactly what a stack tracks.
Best Practices
- Use a list or
deque; both give O(1) push/pop at the end. - Always check for empty before popping to avoid errors.
- Never pop from the front of a list for a stack — that would be O(n).
- Reach for a stack whenever the problem needs "most recent first": undo, DFS, parsing.
Try It Yourself
Exercise 1: Use a stack to reverse the string "hello".
Show solution
stack = list("hello")
out = ""
while stack:
out += stack.pop()
print(out) # ollehExercise 2: Which data structure does a program use to track function calls?
Show solution
A stack — the "call stack". The most recent call is finished (popped) first.
📘 Real-World Deep Dive
Knowing <strong>DSA Stacks (algorithms & data structures)</strong> well is what turns algorithms & data structures from a curiosity into a daily tool — you'll reach for it in nearly every real project.
Real-Life Scenario
An end-to-end usage of DSA Stacks that you'd actually see in a data pipeline or analytics notebook.
Real-Life Example
class Stack:
def __init__(self): self._data = []
def push(self, v): self._data.append(v)
def pop(self): return self._data.pop()
def __len__(self): return len(self._data)
s = Stack()
for ch in "abcde":
s.push(ch)
rev = "".join(s.pop() for _ in range(len(s)))
print(rev)Expected Output
(see source)Common mistakes
- Off-by-one errors in binary-search: the standard idiom is
while lo <= hiwithmid = (lo + hi) // 2. - Recursive algorithms blow the stack for n > ~10⁴; convert to iterative with an explicit stack.
- Comparison algorithms (
sorted(iterable)) are stable by default in Python — surprising for Java/C++ users. - Treating DSA Stacks as a black box without reading the docs — the API has subtle defaults that bite when you scale.
🚀 Performance & Best Practices
- Use
bisect.bisect_left/bisect_rightinstead of writing your own binary search. - Convert a sorted search into a tuple-access pattern with
numpy.searchsortedfor huge arrays. - Use
heapqfor priority queues instead of maintaining a sorted list manually. - When working with algorithms & data structures, prefer vectorised / batched operations over Python loops.
🧪 Try It Yourself
- Reproduce the snippet on a representative slice of your own data.
- Profile the snippet with
cProfileortimeitand find the single biggest improvement. - Generalise the snippet into a small, reusable function you can drop into future projects.