How to Check for Balanced Parentheses in Python
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:
- Every opening bracket has a corresponding closing bracket.
- 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:
- When encountering an opener (
(,{,[), push it onto the stack - 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
- 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 = []
# Map each closer to its corresponding opener
pairs = {
')': '(',
'}': '{',
']': '['
}
for char in expression:
if char in pairs.values():
# It's an opener - push to stack
stack.append(char)
elif char in pairs:
# It's a closer - check for match
if not stack or stack[-1] != pairs[char]:
return False
stack.pop()
# Valid only if all openers were closed
return len(stack) == 0
# Test cases
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:
# ❌ WRONG APPROACH
def is_balanced_wrong(s: str) -> bool:
return s.count('(') == s.count(')')
The string ([)] has equal counts of each bracket type, but it's invalid because:
- The
[is incorrectly closed by) - The
(is incorrectly closed by]
Only a stack can detect this ordering violation.
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}
# Test with errors
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()
# Ignore all other characters
return len(stack) == 0
# Works with real expressions
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):
# Handle string literals (skip their contents)
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
# Normal bracket checking
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.