Python
Stack
Data Structures
Programming
Tutorial

Implementing Stack with Python

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

A stack is one of the simplest and most useful data structures in programming. It follows the Last In, First Out rule, which means the most recently added item is the first one removed. That behavior shows up everywhere, from function-call management to undo systems and expression parsing.

Python makes stack implementation straightforward, but there is still value in being explicit about the operations and tradeoffs. A good stack interface should make push, pop, peek, and emptiness checks obvious, and it should fail predictably when the caller pops from an empty structure.

Use a List for the Simplest Stack

For most Python programs, a plain list is a perfectly good stack. Appending to the end and popping from the end are both efficient operations.

python
1stack = []
2
3stack.append("first")
4stack.append("second")
5stack.append("third")
6
7print(stack.pop())   # third
8print(stack[-1])     # second
9print(len(stack))    # 2

This is the simplest implementation because Python lists already give you the exact operations a stack needs.

A few rules keep this safe:

  • use append for push
  • use pop with no index for pop
  • use stack[-1] only after checking the stack is not empty

Avoid insert(0, value) and pop(0) for stack behavior. Those act on the front of the list and are slower because the remaining items have to shift.

Wrap the Behavior in a Class

If the stack is part of a larger program, a small class makes the interface clearer and protects callers from working with raw list internals.

python
1class Stack:
2    def __init__(self):
3        self._items = []
4
5    def push(self, item):
6        self._items.append(item)
7
8    def pop(self):
9        if self.is_empty():
10            raise IndexError("pop from empty stack")
11        return self._items.pop()
12
13    def peek(self):
14        if self.is_empty():
15            raise IndexError("peek from empty stack")
16        return self._items[-1]
17
18    def is_empty(self):
19        return len(self._items) == 0
20
21    def size(self):
22        return len(self._items)

Usage stays clean:

python
1s = Stack()
2s.push(10)
3s.push(20)
4print(s.peek())
5print(s.pop())
6print(s.size())

This pattern is useful when you want a stable API, type hints, logging, or validation around stack operations.

deque Is Another Good Option

The standard library also provides collections.deque, which is excellent when you need fast push and pop operations from either end.

python
1from collections import deque
2
3stack = deque()
4stack.append("a")
5stack.append("b")
6
7print(stack.pop())
8print(stack[-1])

For a pure stack, list and deque are both reasonable. Lists are simpler and very common. deque becomes especially attractive when the same structure might later need queue-like behavior as well.

A Practical Example: Balanced Parentheses

Stacks are easiest to understand when they solve a real problem. A classic example is checking whether parentheses are balanced.

python
1def is_balanced(text: str) -> bool:
2    stack = []
3    pairs = {")": "(", "]": "[", "}": "{"}
4
5    for char in text:
6        if char in "([{" :
7            stack.append(char)
8        elif char in ")]}":
9            if not stack or stack.pop() != pairs[char]:
10                return False
11
12    return len(stack) == 0
13
14print(is_balanced("(a + b) * [c - d]"))
15print(is_balanced("(a + b]"))

This works because the most recent opening bracket must match the next closing bracket, which is exactly a Last In, First Out rule.

Think About Error Handling

An empty-stack pop is not just a small edge case. It is part of the interface contract. Decide early whether your stack should raise an exception, return None, or use a sentinel value.

Raising IndexError is the most Pythonic default because it clearly signals misuse and matches existing sequence behavior. Returning None is sometimes convenient, but it can hide bugs if None is also a valid value.

If you add type hints, the contract becomes even clearer:

python
from typing import Generic, TypeVar

T = TypeVar("T")

You can then build a generic stack class if the project benefits from stronger typing.

Common Pitfalls

The most common mistake is using the front of a list as the top of the stack. That works functionally, but it is a poorer performance choice than using the end.

Another mistake is peeking or popping without handling the empty case. Stack code often sits inside parsers and evaluators, so a clear error message matters.

A third issue is exposing the backing list publicly and letting other parts of the program mutate it directly. That defeats the point of having a stack abstraction.

Summary

  • A stack follows the Last In, First Out rule.
  • In Python, a list with append and pop is the simplest stack implementation.
  • A small class makes the interface clearer for larger programs.
  • 'collections.deque is also a strong option for stack behavior.'
  • Always define what should happen when callers pop or peek on an empty stack.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.