Skip to content

How to Check for Balanced Parentheses in Python

Learn how to validate balanced parentheses in Python using the stack algorithm to detect proper nesting and matching pairs in expressions, code blocks, and more.

Validating whether a string has balanced parentheses, where every opening bracket has a matching closing bracket in the correct order, is a fundamental computer science problem. It's used in compilers, math expression evaluators, and syntax validators.

A string is balanced if:

  1. Every opening bracket has a corresponding closing bracket.
  2. Brackets are closed in the correct order (properly nested).

The only robust solution that handles multiple bracket types and proper nesting is the Stack algorithm.

Understanding the Stack Algorithm

A Stack follows the LIFO (Last-In, First-Out) principle:

  1. When encountering an opener ((, {, [), push it onto the stack
  2. When encountering a closer (), }, ]), check the top of the stack:
    • If it matches the corresponding opener, pop the stack
    • If it doesn't match (or stack is empty), the string is invalid
  3. After processing all characters, the string is valid only if the stack is empty

Basic Implementation

def is_balanced(expression: str) -> bool:
    """Check if parentheses in expression are balanced."""
    stack = []
    
    pairs = {
        ')': '(',
        '}': '{',
        ']': '['
    }

    for char in expression:
        if char in pairs.values():
            stack.append(char)
            
        elif char in pairs:
            if not stack or stack[-1] != pairs[char]:
                return False
            stack.pop()

    return len(stack) == 0


print(is_balanced("{[()]}"))   # True
print(is_balanced("([)]"))     # False (wrong nesting)
print(is_balanced("((()"))     # False (unclosed)
print(is_balanced(""))         # True (empty is valid)

Output:

True
False
False
True

Detailed Trace Example

For input {[()]}:

Step Char Action Stack
1 { Push opener ['{']
2 [ Push opener ['{', '[']
3 ( Push opener ['{', '[', '(']
4 ) Pop (matches () ['{', '[']
5 ] Pop (matches [) ['{']
6 } Pop (matches {) []
End Stack empty ✅ Valid

Why Simple Counting Fails

A common beginner mistake is counting brackets:

def is_balanced_wrong(s: str) -> bool:
    return s.count('(') == s.count(')')

Enhanced Implementation with Error Details

Return specific information about what went wrong:

def validate_brackets(expression: str) -> dict:
    """Validate brackets and return detailed results."""
    stack = []
    pairs = {')': '(', '}': '{', ']': '['}
    openers = set(pairs.values())

    for i, char in enumerate(expression):
        if char in openers:
            stack.append((char, i))

        elif char in pairs:
            if not stack:
                return {
                    'valid': False,
                    'error': f"Unexpected '{char}' at position {i}",
                    'position': i
                }

            top_char, top_pos = stack[-1]
            if top_char != pairs[char]:
                return {
                    'valid': False,
                    'error': f"Mismatched '{top_char}' at {top_pos} with '{char}' at {i}",
                    'position': i
                }
            stack.pop()

    if stack:
        unclosed = stack[-1]
        return {
            'valid': False,
            'error': f"Unclosed '{unclosed[0]}' at position {unclosed[1]}",
            'position': unclosed[1]
        }

    return {'valid': True, 'error': None, 'position': None}


print(validate_brackets("([)]"))
print(validate_brackets("((()"))

Output:

{'valid': False, 'error': "Mismatched '[' at 1 with ')' at 2", 'position': 2}
{'valid': False, 'error': "Unclosed '(' at position 1", 'position': 1}

Handling Mixed Content

Real-world strings contain more than just brackets. Ignore non-bracket characters:

def is_balanced_expression(expression: str) -> bool:
    """Check balance in expressions with mixed content."""
    stack = []
    pairs = {')': '(', '}': '{', ']': '['}
    openers = set(pairs.values())

    for char in expression:
        if char in openers:
            stack.append(char)
        elif char in pairs:
            if not stack or stack[-1] != pairs[char]:
                return False
            stack.pop()

    return len(stack) == 0


print(is_balanced_expression("func(arr[0], {key: value})"))     # True
print(is_balanced_expression("if (x > 0) { return arr[i]; }"))  # True
print(is_balanced_expression("print('Hello (World'"))           # False

Validating Code Blocks

Check if code has balanced braces:

def validate_code_blocks(code: str) -> bool:
    """Validate balanced brackets in code, ignoring strings."""
    stack = []
    pairs = {')': '(', '}': '{', ']': '['}
    openers = set(pairs.values())
    
    in_string = False
    string_char = None

    for i, char in enumerate(code):
        if char in ('"', "'") and (i == 0 or code[i-1] != '\\'):
            if not in_string:
                in_string = True
                string_char = char
            elif char == string_char:
                in_string = False
            continue

        if in_string:
            continue

        if char in openers:
            stack.append(char)
        elif char in pairs:
            if not stack or stack[-1] != pairs[char]:
                return False
            stack.pop()

    return len(stack) == 0


code = '''
def example():
    data = {"key": "value with (parentheses)"}
    return data["key"]
'''
print(validate_code_blocks(code))  # True

Using deque for Performance

For very long strings, collections.deque is slightly faster:

from collections import deque


def is_balanced_deque(expression: str) -> bool:
    """Balanced check using deque for better performance."""
    stack = deque()
    pairs = {')': '(', '}': '{', ']': '['}

    for char in expression:
        if char in pairs.values():
            stack.append(char)
        elif char in pairs:
            if not stack or stack[-1] != pairs[char]:
                return False
            stack.pop()

    return len(stack) == 0

Common Test Cases

Input Expected Reason
() True Simple match
()[]{} True Sequential pairs
{[()]} True Nested correctly
([)] False Crossed nesting
((( False Unclosed openers
))) False No matching openers
"" True Empty string is valid
({}) True Mixed nesting

Summary

  • Use a Stack (LIFO) to track opening brackets.
  • Push openers, pop and verify on closers.
  • Simple counting fails to detect nesting order violations.
  • The stack must be empty at the end for valid input.
  • Consider ignoring non-bracket characters for real-world expressions.
Navigation