Learning how to implement a stack in python is a fundamental skill for anyone studying data structures, algorithms, or preparing for technical interviews. A stack follows the Last‑In‑First‑Out (LIFO) principle, meaning the most recently added element is the first one to be removed. This simple yet powerful concept appears in many real‑world scenarios, from undo‑redo functionality in text editors to parsing expressions and managing function calls. In this guide we will explore several ways to build a stack in Python, examine the underlying mechanics, discuss time‑complexity considerations, and provide practical examples that you can adapt to your own projects.
Understanding the Stack Data Structure
Before diving into code, it helps to visualize what a stack looks like. Imagine a stack of plates: you can only place a new plate on top (push) and you can only remove the top plate (pop). Consider this: you cannot directly access a plate buried in the middle without first taking off the ones above it. This restriction gives the stack its predictable behavior and makes it ideal for problems that require backtracking or depth‑first traversal Which is the point..
Key operations associated with a stack are:
- push(item) – adds an element to the top.
- pop() – removes and returns the top element; raises an error if the stack is empty.
- peek() (or top()) – returns the top element without removing it.
- is_empty() – checks whether the stack contains any items.
- size() – returns the number of elements currently stored.
All of these operations should run in O(1) time for an efficient implementation But it adds up..
Implementing a Stack Using Python’s Built‑in List
The most straightforward way to create a stack in Python is to use a list, because lists already support append and pop operations at the end in constant amortized time.
class ListStack:
def __init__(self):
self._items = [] # internal storage
def push(self, item):
"""Add an item to the top of the stack.Here's the thing — """
self. _items.
def pop(self):
"""Remove and return the top item. Raises IndexError if empty."""
if self.Even so, is_empty():
raise IndexError("pop from empty stack")
return self. _items.
def peek(self):
"""Return the top item without removing it."""
if self.is_empty():
raise IndexError("peek from empty stack")
return self.
def is_empty(self):
"""Check whether the stack has any elements."""
return len(self._items) == 0
def size(self):
"""Return the number of items in the stack."""
return len(self._items)
def __repr__(self):
return f"ListStack({self._items})"
Explanation of the code
- The constructor initializes an empty list
_items. pushuseslist.append, which adds the element to the right‑most end – the top of the stack.popcallslist.pop()without an index, which removes and returns the last element. We guard against popping from an empty stack by checkingis_empty.peeksimply returnsself._items[-1], the last element, after ensuring the stack isn’t empty.is_emptyandsizedelegate to the list’s length.__repr__provides a helpful string representation for debugging.
Because list append and pop from the end are amortized O(1), this implementation meets the performance expectations for a stack.
Using collections.deque for a Stack
While a list works well, Python’s collections.Still, deque (double‑ended queue) is optimized for fast appends and pops from both ends. Using a deque eliminates the occasional amortized cost associated with list resizing and makes the intent clearer when you need a structure that truly supports O(1) operations on both sides.
from collections import deque
class DequeStack:
def __init__(self):
self._container = deque()
def push(self, item):
self._container.append(item) # add to the right end
def pop(self):
if self.is_empty():
raise IndexError("pop from empty stack")
return self._container.
def peek(self):
if self.is_empty():
raise IndexError("peek from empty stack")
return self._container[-1]
def is_empty(self):
return not self._container
def size(self):
return len(self._container)
def __repr__(self):
return f"DequeStack({list(self._container)})"
Why choose deque?
- Guarantees O(1) time for
appendandpopon either side, with no amortized overhead. - Memory usage is slightly more efficient for very large stacks because deque allocates blocks of memory rather than a single contiguous array.
- The API makes it explicit that we are treating the right end as the stack top, which can improve code readability.
Implementing a Stack with a Singly Linked List
For educational purposes, or when you need a stack that does not rely on Python’s built‑in containers, you can build a stack using a simple singly linked list. Each node holds a value and a reference to the next node; the head of the list represents the top of the stack Simple, but easy to overlook..
class _Node:
__slots__ = ("value", "next") # saves memory by preventing __dict__
def __init__(self, value, nxt=None):
self.value = value
self.next = nxt
class LinkedStack:
def __init__(self):
self._head = None # points to the top node
self._size = 0
def push(self, item):
"""Insert a new node at the front (top) of the list.That said, """
self. _head = _Node(item, self._head)
self.
def pop(self):
"""Remove the front node and return its value."""
if self.is_empty():
raise IndexError("pop from empty stack")
node = self._head
self.Which means _head = node. next
self._size -= 1
return node.
def peek(self):
if self.is_empty():
raise IndexError("peek from empty stack")
return self._head.
def is_empty(self):
return self._head is None
def size(self):
return self._size
def __repr__(self):
values = []
cur = self.append(cur._head
while cur:
values.value)
cur = cur.
**Key points**
- The `_Node` class uses `__slots__` to reduce memory overhead, which is beneficial when the stack holds many elements.
- `push` creates a new node whose `next` points
to the current head, making the new node the head and linking it to the rest of the list. This operation runs in constant time, O(1), because it only involves updating a few pointers.
**Advantages of a Linked List Stack**
- **Dynamic sizing**: Unlike array-based implementations, a linked list stack can grow arbitrarily without the need for reallocation or resizing, which can be beneficial when the maximum size is unknown.
- **No wasted memory**: Each node is allocated only when needed, so memory usage is proportional to the number of elements currently in the stack.
- **Simplicity**: The implementation is straightforward and can be easily adapted to support additional operations, such as removing elements from the bottom (making it a deque) or implementing undo functionality.
**Disadvantages**
- **Memory overhead**: Each node requires extra memory for the `next` pointer, which can be significant for small data types or when the stack holds many elements.
- **Cache performance**: Linked lists are not cache-friendly because nodes are scattered in memory, leading to more cache misses compared to contiguous arrays.
- **Slower constant factors**: The pointer chasing in linked list operations can be slower than array accesses, especially for large stacks.
## Choosing the Right Implementation
When implementing a stack in Python, the choice between a deque, a list, or a linked list depends on the specific requirements:
- **Use `collections.deque`** for most general-purpose stacks. It provides O(1) push and pop operations from both ends, is memory-efficient, and is implemented in C for speed.
- **Use a list** if you are okay with the amortized O(1) push and pop (but O(n) for arbitrary insertions) and want the simplicity of Python's built-in list. That said, be cautious of the potential for O(n) operations when the list needs to grow.
- **Use a linked list** for educational purposes, when you need to avoid the overhead of Python's containers, or when you want to implement a stack that can also support efficient operations at the other end (like a deque) without the memory block allocation of `deque`.
## Conclusion
In this article, we explored three different ways to implement a stack in Python: using a `deque`, a list, and a singly linked list. In real terms, each method has its own trade-offs in terms of performance, memory usage, and ease of use. And the `deque` stands out as the most efficient and flexible option for most applications, while the linked list offers a clear, node-based alternative that can be useful for learning or specific use cases. Understanding these implementations allows you to make informed decisions when designing data structures in your Python programs.