Skip to content

How to Implement a Stack Using a List in Python

Learn how to implement a stack in Python using a list with O(1) push/pop operations, including core methods (push, pop, peek, isEmpty, size) and a reusable Stack class.

A stack is a linear data structure that follows the LIFO (Last In, First Out) principle: the last element added is the first one to be removed. Think of a stack of plates: you always take the top plate first, and you always place a new plate on top.

Stacks are used extensively in programming for tasks like undo/redo operations, expression evaluation, backtracking algorithms, and function call management (the call stack). In Python, the simplest and most common way to implement a stack is by using a built-in list, since list.append() and list.pop() both operate in O(1) amortized time on the last element.

This guide covers every core stack operation with clear examples and then brings everything together into a clean, reusable class.

Core Stack Operations

Before diving into code, here are the five fundamental operations every stack supports:

Operation Description
push() Insert an element onto the top of the stack
pop() Remove and return the top element from the stack
top() / peek() Return the top element without removing it
isEmpty() Check whether the stack is empty
size() Return the number of elements in the stack

Push: Adding Elements to the Stack

The push operation adds an element to the top of the stack. Since a Python list grows from left to right, the "top" of the stack is the last element in the list. We use the built-in append() method:

stack = []

stack.append(1)
stack.append(2)
stack.append(3)

print("Stack contents:")
for item in stack:
    print(item)

Output:

Stack contents:
1
2
3

Element 3 is at the top of the stack because it was added last.

Pop: Removing the Top Element

The pop operation removes and returns the element at the top of the stack. Python's list.pop() (with no arguments) removes the last element, which is exactly what we need:

stack = []

stack.append(1)
stack.append(2)
stack.append(3)

print("Initial stack:", stack)
print("Popped element:", stack.pop())
print("Stack after pop:", stack)

Output:

Initial stack: [1, 2, 3]
Popped element: 3
Stack after pop: [1, 2]

Top (Peek): Viewing the Top Element

The top (also called peek) operation returns the element at the top of the stack without removing it. Use index -1 to access the last element in the list:

stack = [1, 2, 3]

def top(stack):
    if len(stack) == 0:
        return None
    return stack[-1]

print("Top element:", top(stack))
print("Stack is unchanged:", stack)

Output:

Top element: 3
Stack is unchanged: [1, 2, 3]

isEmpty: Checking if the Stack Is Empty

The isEmpty check is essential before performing pop or top operations to avoid errors:

stack = []

def is_empty(stack):
    return len(stack) == 0

print(is_empty(stack))  # True

stack.append(10)
print(is_empty(stack))  # False

Output:

True
False

You can also use Python's truthy/falsy evaluation directly:

if not stack:
    print("Stack is empty")

Size: Getting the Number of Elements

Use Python's built-in len() function to get the current number of elements:

stack = [1, 2, 3]
print("Stack size:", len(stack))

Output:

Stack size: 3

Complete Stack Class Implementation

While using standalone functions works for quick scripts, wrapping everything in a class provides better encapsulation, reusability, and a cleaner API. Here is a complete, well-structured Stack class:

class Stack:
    def __init__(self):
        self._items = []

    def push(self, data):
        """Add an element to the top of the stack."""
        self._items.append(data)

    def pop(self):
        """Remove and return the top element. Returns None if empty."""
        if self.is_empty():
            print("Stack is empty; nothing to pop")
            return None
        return self._items.pop()

    def top(self):
        """Return the top element without removing it. Returns None if empty."""
        if self.is_empty():
            return None
        return self._items[-1]

    def is_empty(self):
        """Return True if the stack has no elements."""
        return len(self._items) == 0

    def size(self):
        """Return the number of elements in the stack."""
        return len(self._items)

    def display(self):
        """Print the stack contents from bottom to top."""
        if self.is_empty():
            print("Stack is empty")
        else:
            print("Stack (bottom -> top):", self._items)

    def __str__(self):
        return str(self._items)

Example Usage and Output

s = Stack()

s.push("Alice")
s.push("Bob")
s.push("Charlie")
s.push("Diana")

s.display()
print("Size:", s.size())

print("Popped:", s.pop())

print("Top element:", s.top())

print("Is empty?", s.is_empty())

s.push("Eve")
s.display()

Output:

Stack (bottom -> top): ['Alice', 'Bob', 'Charlie', 'Diana']
Size: 4
Popped: Diana
Top element: Charlie
Is empty? False
Stack (bottom -> top): ['Alice', 'Bob', 'Charlie', 'Eve']

Step-by-step explanation:

  1. Four names are pushed onto the stack. "Diana" is at the top.
  2. size() returns 4.
  3. pop() removes and returns "Diana", the most recently added element.
  4. top() returns "Charlie", the new top element, without removing it.
  5. is_empty() returns False because three elements remain.
  6. "Eve" is pushed, becoming the new top element.

Common Mistake: Passing the List as a Parameter

A design flaw seen in many beginner implementations is passing the internal list as an argument to every method:

class BadStack:
    def push(self, st, data):
        st.append(data)

    def pop(self, st):
        return st.pop()

external_list = []
s = BadStack()
s.push(external_list, 10)

This defeats the purpose of using a class because the data is not encapsulated. Anyone can modify external_list directly, breaking the stack's integrity.

class Stack:
    def __init__(self):
        self._items = []

    def push(self, data):
        self._items.append(data)

    def pop(self):
        if self.is_empty():
            return None
        return self._items.pop()

The stack owns its data. External code interacts only through the defined methods.

Time and Space Complexity

Operation Time Complexity Explanation
push() O(1) amortized list.append() is O(1) amortized; occasional resizing is O(n) but averages out
pop() O(1) Removing the last element requires no shifting
top() O(1) Accessing list[-1] is a direct index lookup
is_empty() O(1) len() on a list is a constant-time operation
size() O(1) len() on a list is a constant-time operation

Space Complexity: O(n), where n is the number of elements stored in the stack.

Conclusion

Python's built-in list is an excellent choice for implementing a stack thanks to its O(1) amortized append() and O(1) pop() operations.

  • By wrapping these operations in a well-designed class, you get a clean, reusable stack with proper encapsulation and safety checks.
  • This implementation is suitable for learning, coding interviews, and most practical applications.
  • For thread-safe scenarios, consider using queue.LifoQueue from Python's standard library instead.
Navigation