How to create a stack in Python
Learn to create a stack in Python using various methods. Get tips, see real-world examples, and learn to debug common errors.

A stack is a core data structure that manages data with a Last-In, First-Out (LIFO) method. Python lacks a native stack type, but you can create one with lists or other modules.
In this article, you'll learn several techniques to build a stack. We'll cover practical tips, explore real-world applications, and offer debugging advice to help you implement this data structure effectively in your projects.
Using a list as a stack
stack = []
stack.append("first")
stack.append("second")
print(stack)
print(stack.pop())
print(stack)--OUTPUT--['first', 'second']
second
['first']
Using a Python list is the most direct way to build a stack. The list's methods naturally align with the required stack operations, making it an intuitive choice for those familiar with creating lists in Python.
- The
append()method pushes a new item onto the top of the stack. - The
pop()method, when called without an index, removes the last item added. This perfectly executes the Last-In, First-Out (LIFO) principle.
This is why after appending 'second', it's the first element to be removed by pop(), leaving only 'first' in the stack.
Alternative stack implementations
While lists are a convenient starting point, Python's standard library provides more robust alternatives for implementing stacks, each with distinct advantages. For other data structure implementations, you might also explore implementing linked list in Python.
Using collections.deque for better performance
from collections import deque
stack = deque()
stack.append("first")
stack.append("second")
print(stack)
print(stack.pop())
print(stack)--OUTPUT--deque(['first', 'second'])
second
deque(['first'])
The collections.deque object, a double-ended queue, is specifically designed for fast appends and pops from either end. This gives it a memory-efficient performance edge over standard lists, which are dynamic arrays and can slow down when they need to resize internally.
- Using a
dequeensures that adding and removing items are consistently fast operations. - This makes it a more robust choice for implementing a stack, especially when performance is a concern or you're working with many items.
Creating a custom Stack class
class Stack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop() if self.items else None
stack = Stack()
stack.push(42)
stack.push(73)
print(stack.items)
print(stack.pop())--OUTPUT--[42, 73]
73
For more control, you can define a custom Stack class. This approach wraps the stack's logic in a reusable object, making your code cleaner and more intuitive. It uses a list internally to hold the data, but provides its own methods for stack operations.
- The
push()method adds an item to the stack. - The
pop()method removes an item, but with a key improvement—it returnsNoneif the stack is empty, which prevents the errors you'd get from a plain list.
Using queue.LifoQueue for thread safety
from queue import LifoQueue
stack = LifoQueue()
stack.put("first")
stack.put("second")
print(f"Size: {stack.qsize()}")
print(f"Popped: {stack.get()}")
print(f"Size: {stack.qsize()}")--OUTPUT--Size: 2
Popped: second
Size: 1
When your application involves multiple threads, queue.LifoQueue is the ideal implementation. It's specifically designed to be thread-safe, which means it automatically handles the complex locking required to prevent data corruption when different threads access the stack simultaneously.
- You use the
put()method to add items to the stack. - The
get()method removes the most recently added item, ensuring LIFO behavior.
This makes it a robust choice for any concurrent programming task where data integrity is critical, similar to techniques used when implementing priority queue in Python.
Advanced stack techniques
Building on the custom class, you can implement advanced features like indexed access with __getitem__, enforce a size limit, or add efficient min() and max() operations through vibe coding.
Implementing a stack with __getitem__ access
class IndexableStack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def __getitem__(self, index):
return self.items[-(index+1)]
stack = IndexableStack()
stack.push("bottom")
stack.push("middle")
stack.push("top")
print(f"Top item: {stack[0]}")
print(f"Middle item: {stack[1]}")--OUTPUT--Top item: top
Middle item: middle
By implementing the __getitem__ dunder method, you can make your stack behave like a list for indexed access. It’s a powerful way to “peek” at items without removing them. The logic self.items[-(index+1)] cleverly reverses the standard list indexing.
stack[0]now refers to the top of the stack—the last item pushed.stack[1]refers to the item just below the top.
This creates a more intuitive interface for inspecting the stack's contents from top to bottom.
Creating a stack with maximum size limit
class BoundedStack:
def __init__(self, max_size):
self.items = []
self.max_size = max_size
def push(self, item):
if len(self.items) < self.max_size:
self.items.append(item)
return True
return False
stack = BoundedStack(2)
print(stack.push("first"))
print(stack.push("second"))
print(stack.push("third")) # Won't be added--OUTPUT--True
True
False
You can create a stack with a fixed capacity by adding a size check to the push() method. The BoundedStack class stores a max_size value during initialization. Before adding a new item, push() checks if the current number of items is less than this limit.
- If there's room, the item is added, and the method returns
True. - If the stack is full, the item is rejected, and the method returns
False.
This approach is great for managing memory or preventing the stack from growing indefinitely.
Implementing a stack with min() and max() operations
class MinMaxStack:
def __init__(self):
self.stack = []
self.min_stack = []
self.max_stack = []
def push(self, val):
self.stack.append(val)
if not self.min_stack or val <= self.min_stack[-1]:
self.min_stack.append(val)
if not self.max_stack or val >= self.max_stack[-1]:
self.max_stack.append(val)
stack = MinMaxStack()
for num in [3, 1, 5, 2]:
stack.push(num)
print(f"Min: {stack.min_stack[-1]}, Max: {stack.max_stack[-1]}")--OUTPUT--Min: 1, Max: 5
This implementation cleverly tracks the minimum and maximum values in the stack with constant-time complexity, which is far more efficient than repeatedly scanning the entire stack. The magic lies in using two additional helper stacks alongside the main one.
- A
min_stackstores the running minimum. A new value is pushed onto it only if it's less than or equal to the current minimum. - Similarly, a
max_stacktracks the running maximum.
This design ensures the current min and max are always available right at the top of their respective stacks.
Move faster with Replit
Replit is an AI-powered development platform where Python dependencies come pre-installed, so you can skip setup and start coding instantly. This lets you move from learning individual techniques to building complete applications.
Instead of piecing together stack implementations, you can use Agent 4 to take an idea to a working product. Describe the app you want to build, and it will handle the code, databases, APIs, and deployment. For example, you could ask it to create:
- A syntax checker that uses a stack to validate balanced parentheses in code.
- A simple "undo" feature for a text editor, where each action is pushed onto a stack.
- A browser history tool that lets you navigate back through visited pages using a LIFO stack.
Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.
Common errors and challenges
When implementing stacks, a few common pitfalls can trip you up, but they're easy to avoid with a little foresight.
- Handling empty stack exceptions: Calling
pop()on an empty list-based stack raises anIndexError, which can crash your program. You can prevent this by always checking if the stack contains items before attempting to pop an element. A simple conditional check is all it takes. - Incorrectly accessing the top element: A frequent mistake is using
stack[0]to view the top item. In a list-based stack,stack[0]actually accesses the bottom element. To properly "peek" at the top item without removing it, you should use the index-1, as instack[-1]. - Using
pop(0)instead ofpop(): While it might seem like a small difference, callingpop(0)removes the first item from the list. This implements a First-In, First-Out (FIFO) behavior, effectively turning your stack into a queue. To maintain the Last-In, First-Out (LIFO) principle, you must usepop()with no arguments.
Handling empty stack exceptions when using pop()
A classic mistake is calling pop() on an empty stack, which raises an IndexError and crashes your program. This happens when you try to remove an item that isn't there. The following code demonstrates this exact scenario in a loop.
def process_stack(stack):
while True:
item = stack.pop() # Will raise IndexError when stack is empty
print(f"Processed: {item}")
my_stack = [1, 2, 3]
process_stack(my_stack)
The while True: loop runs indefinitely, attempting to call pop() even after the stack is empty. This inevitably triggers the IndexError. The corrected code below shows how to handle this situation gracefully.
def process_stack(stack):
while stack: # Check if stack is not empty before popping
item = stack.pop()
print(f"Processed: {item}")
my_stack = [1, 2, 3]
process_stack(my_stack)
The corrected code uses the condition while stack:, which is a more Pythonic way to loop. This works because an empty list evaluates to False, automatically stopping the loop when the stack is depleted. This simple check prevents the pop() method from being called on an empty stack, neatly avoiding an IndexError through proper code repair. It's a common pattern you'll use anytime you need to process all items in a stack until it's completely empty.
Incorrectly accessing the top element of a stack
A common mix-up when using lists as stacks is how you “peek” at the top item without removing it. It’s tempting to use stack[0], but this actually grabs the bottom element—the first one you added. The following code demonstrates this error.
stack = [1, 2, 3, 4]
top_element = stack[0] # Incorrect! This accesses the bottom element
print(f"Top element: {top_element}")
This code mistakenly retrieves the value 1 because list indexing starts from the beginning. This gives you the oldest item, not the newest. The following example shows how to correctly access the top element.
stack = [1, 2, 3, 4]
top_element = stack[-1] # Correct! This accesses the top element
print(f"Top element: {top_element}")
The correct way to peek at the top item is with negative indexing. Using stack[-1] grabs the last element of the list, which is exactly what you want since append() adds items to the end. This gives you the most recently added item without removing it. You'll find this technique useful whenever you need to inspect the stack's state before deciding on your next move, like in parsing algorithms or managing application states.
Using pop(0) instead of pop() in a stack implementation
It's easy to mix up pop() and pop(0), but the difference is crucial. Using pop(0) accidentally removes the oldest item, not the newest. This effectively turns your stack into a queue, breaking the LIFO principle. The code below demonstrates this common pitfall.
stack = [1, 2, 3, 4]
top_element = stack.pop(0) # Wrong! This removes the bottom element
print(f"Top element: {top_element}")
print(f"Remaining stack: {stack}")
This code uses pop(0), which pulls the first item added, 1, instead of the last. This breaks the LIFO principle, effectively turning your stack into a queue. The correct implementation is shown below.
stack = [1, 2, 3, 4]
top_element = stack.pop() # Correct! This removes the top element
print(f"Top element: {top_element}")
print(f"Remaining stack: {stack}")
The correct approach uses stack.pop() without any arguments. This removes the last item added to the list—in this case, 4. This action correctly follows the Last-In, First-Out (LIFO) principle, leaving [1, 2, 3] in the stack. Using pop(0) would've incorrectly removed the first element, 1, turning your stack into a queue. This distinction is critical in algorithms where the order of operations matters, like expression evaluation or backtracking.
Real-world applications
With the theory and common errors covered, you can see how stacks form the backbone of many familiar software features.
Checking for balanced parentheses with a stack
This classic algorithm uses a stack to track opening brackets, ensuring each one is properly closed in the reverse order it appeared.
def check_balanced_parentheses(expression):
stack = []
brackets = {')': '(', '}': '{', ']': '['}
for char in expression:
if char in '({[':
stack.append(char)
elif char in ')}]':
if not stack or stack.pop() != brackets[char]:
return False
return len(stack) == 0
print(check_balanced_parentheses("{[()]}"))
print(check_balanced_parentheses("{[(])}"))
The check_balanced_parentheses function validates if an expression's brackets are balanced. It iterates through each character, using a list as a stack to temporarily store any opening brackets it finds.
- If the character is an opening bracket like
(,{, or[, it’s pushed onto the stack. - If it’s a closing bracket, the function pops the last item from the stack and checks if it’s the correct matching pair. The expression is invalid if the stack is empty or the brackets don't match.
After the loop finishes, the function returns True only if the stack is empty, which confirms every bracket was correctly paired.
Implementing undo functionality using a stack
A stack’s LIFO behavior makes it a natural fit for an undo function, where you push the previous state before a change and pop it to revert the action.
class SimpleTextEditor:
def __init__(self):
self.text = ""
self.history = []
def add_text(self, new_text):
self.history.append(self.text)
self.text += new_text
def undo(self):
if self.history:
self.text = self.history.pop()
editor = SimpleTextEditor()
editor.add_text("Hello")
editor.add_text(" World")
print(f"Current text: {editor.text}")
editor.undo()
print(f"After undo: {editor.text}")
The SimpleTextEditor class uses a list named history to keep track of previous text states. It's a straightforward way to build an undo feature.
- Before adding new content, the
add_text()method saves a snapshot of the current text to thehistorylist. - When you call
undo(), it retrieves the most recent snapshot fromhistoryusingpop(). This action restores the text to its state just before the last modification was made.
Get started with Replit
Put your stack knowledge to work. Ask Replit Agent 4 to "build a web app that checks for balanced brackets in code snippets" or "create a browser history simulator with back/forward buttons."
Replit Agent handles the entire development process. It writes the code, debugs errors, and deploys the final app. Start building with Replit.
Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.
Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.



