Hello! In our last lesson, we mastered the art of merging sorted linked lists, tackling the problem with both iterative and recursive approaches and analyzing their space-time trade-offs. That was a great way to solidify your pointer manipulation skills.
Today, we transition from linked lists to another fundamental linear data structure: the stack. You'll discover its core principle, Last-In, First-Out (LIFO), which underpins many algorithms and software features you use daily, from a compiler's syntax checker to your browser's back button. This lesson will equip you to implement a stack efficiently in Python and use it to solve a classic interview problem, "Valid Parentheses."
What is a Stack? The LIFO Principle
A stack is a linear data structure that follows a simple but powerful rule: Last-In, First-Out (LIFO). This means the last element added to the stack is always the first one to be removed.
A common and effective analogy is a stack of plates. You can only add a new plate to the top, and you can only take a plate from the top. You can't pull a plate from the middle or bottom without toppling the whole stack. This "top-only" access is the defining feature of a stack.
Python Stack: Implementing LIFO Data Structures - DataCamp
This DataCamp tutorial provides an excellent introduction to the core concepts of a stack.
Please read the first two sections, "What Is a Stack in Python?" and "Stack vs list". Focus on understanding the LIFO principle and how a stack's restricted access is a feature, not a limitation, when designing certain algorithms.
Core Stack Operations
All stacks, regardless of implementation, are defined by a few core operations. These operations are designed to be extremely fast.
- Push: Adds a new element to the top of the stack.
- Pop: Removes and returns the element currently at the top of the stack. Attempting to pop from an empty stack results in an error condition known as Stack Underflow.
- Peek (or Top): Returns the top element without removing it. This is useful for inspecting the top element before deciding on the next action.
- is_empty: A helper operation that returns true if the stack has no elements, and false otherwise. It's crucial for avoiding stack underflow errors.
- size: Returns the number of elements currently in the stack.

One of the main reasons stacks are so prevalent in algorithm design is their efficiency. Because all operations happen at one end, they can be performed in constant time.
Python Stack: Implementing LIFO Data Structures - DataCamp
The same DataCamp article explains these operations and their performance characteristics in detail.
Read the section "Core Stack Operations and Time Complexity". Pay close attention to the table summarizing the time complexity. The O(1) time for push, pop, and peek is the most important takeaway.
Implementing a Stack in Python
Python doesn't have a built-in stack data type like some other languages. Instead, it provides flexible data structures that can be used to implement a stack. Let's look at the most common options and their trade-offs, which is a key discussion point in technical interviews.
1. Using a list
The most straightforward method is to use Python's built-in list.
pushis implemented withlist.append().popis implemented withlist.pop().peekcan be done by accessing the last elementmy_stack[-1].
While simple and often sufficient for quick scripts or whiteboard problems, there's a performance detail to be aware of. Python lists are dynamic arrays. While append() and pop() from the end are usually O(1), the list might need to be resized and reallocated in memory if it runs out of space, which is an O(n) operation. This makes the performance amortized O(1), not guaranteed O(1).
2. Using collections.deque
For performance-critical applications and robust algorithmic code, collections.deque is the recommended tool. A deque (pronounced "deck," short for double-ended queue) is implemented internally as a doubly-linked list.
This structure allows for highly efficient additions and removals from both ends. For our stack, we use it just like a list:
pushis implemented withdeque.append().popis implemented withdeque.pop().peekis done withmy_stack[-1].
The key advantage is that these operations are guaranteed O(1) time, as there is never a need to shift elements or reallocate the entire structure.
Python's deque: Implement Efficient Queues and Stacks
This Real Python article dives into why deque is the superior choice for implementing stacks and queues.
Read the introductory section "Get Started With Python's deque" to understand what it is and how it differs from a list. Jump to the "Frequently Asked Questions" at the end and read the answer to "Why should you use deque instead of list for queues or stacks?". This nicely summarizes the key takeaway.
3. Using queue.LifoQueue
The standard library also offers queue.LifoQueue. This implementation is specifically designed for multi-threaded programming. It includes locking mechanisms to ensure that operations are thread-safe. However, this safety comes with performance overhead, making it unsuitable for single-threaded algorithmic problems where deque is much faster.
For interview purposes and general problem-solving, collections.deque is almost always the best answer when asked for a Python stack implementation.
Problem-Solving with Stacks: Valid Parentheses
Now, let's apply our knowledge to a classic LeetCode problem: Valid Parentheses. This problem is a perfect illustration of LIFO logic.
The Problem: Given a string s containing just the characters (, ), {, }, [ and ], determine if the input string is valid. An input string is valid if:
- Open brackets must be closed by the same type of brackets.
- Open brackets must be closed in the correct order.
For example, "{[()]}" is valid, but "([)]" is not. The "correct order" rule implies that the most recently opened bracket must be the first one to be closed. This is a direct signal to use a stack.

The algorithm is as follows:
- Create an empty stack.
- Create a hash map to store the matching pairs (e.g.,
')': '(',']': '['). - Iterate through each character of the string.
- If the character is an opening bracket (
(,[,{), push it onto the stack. - If the character is a closing bracket (
),],}):- Check if the stack is empty. If it is, we have a closing bracket with no corresponding opener, so the string is invalid.
- If the stack is not empty, pop the top element.
- Check if this popped element is the correct opening bracket for the current closing bracket (using our hash map). If they don't match, the string is invalid.
- After the loop finishes, if the stack is empty, it means every opening bracket was correctly matched and closed. The string is valid. If the stack is not empty, it means there are unclosed opening brackets, so the string is invalid.
This video from Greg Hogg provides a concise walkthrough of this logic and the Python implementation.
Valid Parentheses - Leetcode 20 - Stacks (Python)
This video from Greg Hogg provides a clear, step-by-step solution to the Valid Parentheses problem.
Watch the initial explanation from the beginning to understand the rules and edge cases. Follow the core logic of using a stack from this segment. He explains how to handle open vs. closed brackets and the final check. Finally, watch the code implementation, where he uses a hash map and a list-as-a-stack to solve the problem, and discusses the complexity.
As a full-stack developer, you can see how this pattern is fundamental. Parsers that validate structured data formats like JSON or XML rely on similar stack-based mechanisms to ensure that every opening tag or bracket has a corresponding closing one in the correct nested order.
Conclusion
In this lesson, we demystified the stack, a cornerstone data structure in computer science. You've learned its defining LIFO principle and how to apply it to solve a common and important interview problem.
Key Takeaways:
- A stack is a linear data structure that operates on a Last-In, First-Out (LIFO) basis.
- The primary operations are Push, Pop, and Peek, all of which have an efficient O(1) time complexity.
- In Python, a stack can be implemented with a
list, butcollections.dequeis the preferred choice for its guaranteed O(1) performance. - Stacks are ideal for problems involving nested structures or "last-opened, first-closed" logic, such as validating parentheses.
Now that we have covered stacks and their LIFO behavior, our next lesson will introduce their counterpart: queues. We will explore the First-In, First-Out (FIFO) principle and see how it's used to model different kinds of problems, like processing tasks in order.