A stack is one of the simplest and most useful data structures in computer science, built around a single rule: Last In, First Out (LIFO). Picture a stack of plates — you can only add a new plate to the top, and you can only remove the topmost plate. You can't pull one out from the middle without disturbing everything above it. That one constraint is the entire definition of a stack, and it turns out to model an enormous number of real problems.

The Three Core Operations

A stack supports three operations, and — this is the important part — all three run in O(1) time, because they only ever touch the top element.

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

    def push(self, value):      # add to the top -- O(1)
        self.items.append(value)

    def pop(self):               # remove and return the top -- O(1)
        if self.is_empty():
            raise IndexError("pop from empty stack")
        return self.items.pop()

    def peek(self):              # look at the top without removing it -- O(1)
        if self.is_empty():
            raise IndexError("peek from empty stack")
        return self.items[-1]

    def is_empty(self):
        return len(self.items) == 0

stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
print(stack.peek())   # see the top
print(stack.pop())    # remove the top
print(stack.pop())    # remove the new top

Output:

3
3
2

Notice that a stack doesn't need a specialized data structure under the hood — a plain array (or Python list) with operations restricted to one end already gives you O(1) push/pop, because adding or removing from the end of a dynamic array is cheap, unlike the middle. See the full stack implementation using an array for a complete walkthrough.

Real-World Uses of Stacks

Stacks show up constantly, often invisibly. Undo functionality in an editor: every action gets pushed onto a stack, and "undo" pops the most recent one off. The function call stack every program uses: when function A calls function B, B gets "pushed" on top, and when B returns, it's "popped" off, resuming A exactly where it left off — this is also why deep recursion can cause a stack overflow. Expression evaluation and parsing: checking that parentheses are balanced, or evaluating a mathematical expression, is a classic stack problem — push an opening bracket, pop when you see the matching closer.

A Quick Balanced-Parentheses Example

def is_balanced(expression):
    stack = []
    pairs = {')': '(', ']': '[', '}': '{'}
    for char in expression:
        if char in '([{':
            stack.append(char)
        elif char in ')]}':
            if not stack or stack.pop() != pairs[char]:
                return False
    return len(stack) == 0

print(is_balanced("[(1+2)*{3}]"))  # True
print(is_balanced("[(1+2]"))       # False -- mismatched

Output:

True
False

Whenever a problem involves "most recently added, first removed" or "matching nested pairs," a stack is almost always the right tool to reach for.