Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 38

Stack Applications: Parentheses Balancing

Recommended resources

Introduction

Hello! In our last lesson, you learned about the stack data structure, its Last-In, First-Out (LIFO) principle, and how to implement one using a Python list. We saw that the core operations—push, pop, and peek—are simple yet powerful.

Today, we'll put that knowledge into practice. Your learning outcome is to solve a problem using a stack, such as checking for balanced parentheses. This is a classic problem in computer science and a very common question in technical interviews, making it directly relevant to your goals. We'll discover how the stack's LIFO property makes it the perfect tool for tracking nested structures.


1. Defining the "Balanced Parentheses" Problem

First, what does it mean for parentheses (and brackets, and braces) to be "balanced"? It's more than just having an equal number of opening and closing symbols. There are specific rules that must be followed.

A string of brackets is considered balanced if:

  1. Every opening bracket has a corresponding closing bracket of the same type. For example, ( must be closed by ), not ].
  2. Brackets are closed in the correct order. The most recently opened bracket must be the first one to be closed.

Let's look at some examples.

Matched vs. Balanced Brackets
This table clarifies the distinction between brackets that are simply "matched" by type and those that are correctly "balanced" by order. For instance, `([)]` has matching types, but the closing order is wrong, so it's unbalanced.

To solidify this definition, let's watch a short video that breaks down these rules with clear examples.

Valid Parentheses - Leetcode 20 - Stacks (Python)

The video 'Valid Parentheses' from the Greg Hogg YouTube channel clearly defines the three criteria for a string to be considered valid.

Watch from the beginning to 02:59. The presenter explains the problem and details the rules for a valid string, showing examples for each.


2. Developing an Algorithm with a Stack

How can we programmatically check for these rules? A first thought might be to just count the number of opening and closing brackets of each type. However, as the table above shows with ([)], this isn't enough. The order is crucial.

Consider the string {[()]}. As we scan from left to right, notice the pattern of how brackets are closed:

  • The ( is opened.
  • The ) is closed. It matches the most recent opening bracket, (.
  • The [ is opened.
  • The ] is closed. It matches the most recent unclosed opening bracket, [.
  • The { is opened.
  • The } is closed. It matches the most recent unclosed opening bracket, {.

This pattern—"the last one opened is the first one closed"—is exactly the LIFO behavior of a stack. This gives us a powerful clue for our algorithm.

Let's watch a video that builds this algorithm from the ground up.

Check for balanced parentheses using stack

This video from mycodeschool explains why simply counting brackets fails and then develops the stack-based algorithm step-by-step. It starts with the core idea and gradually refines it.

Watch from 04:06 to 09:55. Pay close attention to how the presenter uses a list to keep track of unclosed opening brackets and then explicitly connects this behavior to the push and pop operations of a stack.

To summarize the algorithm:

  1. Create an empty stack.
  2. Iterate through the string one character at a time.
  3. If the character is an opening bracket ((, {, [), push it onto the stack.
  4. If the character is a closing bracket (), }, ]):
    a. Check if the stack is empty. If it is, there's no matching opener, so the string is unbalanced.
    b. If the stack is not empty, pop the top element. If this popped element is not the corresponding opening bracket for the current closing bracket, the string is unbalanced.
  5. After the loop finishes, if the stack is empty, it means every opening bracket found its match. The string is balanced!
  6. If the stack is not empty, it means there are leftover opening brackets that were never closed. The string is unbalanced.

This image provides a great visual trace of this process.

Balanced Parentheses using a Stack
This diagram shows the state of the stack as we process the string `[ { ( ) } ]`. Opening brackets are pushed, and when a closing bracket is found, the top of the stack is popped and checked for a match.

3. Implementing the Solution in Python

Now, let's translate this algorithm into Python code. We'll use a list as our stack, just as you learned in the previous lesson. A dictionary will also be very helpful to map closing brackets to their opening counterparts, making the matching logic clean and easy to read.

This video demonstrates a concise and effective Python implementation.

Valid Parentheses - Leetcode 20 - Stacks (Python)

Let's return to the Greg Hogg video. He will now write the Python code for the solution, using a dictionary for bracket mapping and a list to act as the stack.

Watch from 05:20 to 08:14. Notice how the dictionary simplifies checking for pairs and how the code handles the different conditions (pushing openers, handling closers, and checking for an empty stack).

Here is a complete, commented function based on the logic from the video.

def is_balanced(expression):
    """
    Checks if a string of brackets is balanced using a stack.
    """



    # The stack will store the opening brackets we encounter.
    stack = []




    # A dictionary to map closing brackets to their opening counterparts.
    # This makes checking for a match very efficient.
    pairs = {')': '(', '}': '{', ']': '['}




    # Iterate through each character in the expression string.
    for char in expression:



        # If the character is an opening bracket, push it onto the stack.
        # We can check this by seeing if it's in the *values* of our pairs dict.
        if char in pairs.values():
            stack.append(char)
        



        # If the character is a closing bracket...
        elif char in pairs.keys():



            # The string is unbalanced if:
            # 1. The stack is empty (a closing bracket with no opener before it).
            # 2. The opener we pop from the stack doesn't match the current closer.
            if not stack or stack.pop() != pairs[char]:
                return False
        



        # We simply ignore any other characters that are not brackets.




    # After the loop, the string is balanced only if the stack is empty.
    # An empty stack means every opener found a matching closer.
    return not stack # `not []` is True; `not ['(', ...]` is False

4. Your Turn: Trace the Execution

Reading code is one thing; seeing it in action is another. To truly understand how it works, trace the is_balanced function with the following inputs. On a piece of paper or in a text editor, keep track of the stack's state after processing each character.

  1. Input: "{[()]}" (should return True)
  2. Input: "{[(])}" (should return False)

Let's do the first character of the second example together to get you started:

  • Input: "{[(])}"
  • char is {. This is an opening bracket (in pairs.values()).
  • Action: stack.append('{').
  • Stack state: ['{']

Now, continue this process for the rest of the characters in both strings. See if your final result matches the expected output and try to pinpoint exactly where the logic fails for the unbalanced string. This exercise is crucial for building the problem-solving intuition needed for interviews.


Conclusion

Congratulations! You've successfully used a stack to solve a non-trivial algorithmic problem. This exercise demonstrates how choosing the right data structure can turn a complex-sounding problem into a straightforward and elegant solution.

Key Takeaways:

  • The balanced parentheses problem requires checking for both correct type and correct order of brackets.
  • The stack's LIFO (Last-In, First-Out) property is perfect for this task because the most recently opened bracket must be the first to be closed.
  • The algorithm is: push opening brackets, and when you see a closing bracket, pop and check for a match.
  • An empty stack at the end signifies a balanced string; a non-empty stack signifies an unbalanced one.
  • Using a dictionary to map closing to opening brackets makes the implementation clean and efficient.

Preview of the Next Lesson:

We've now seen how the LIFO principle of stacks is useful. But what if we need the opposite behavior? In the next lesson, we will explore the queue data structure, which operates on a FIFO (First-In, First-Out) principle. You'll learn what a queue is and how to implement one in Python using the collections.deque object.