If an exam or interview asks you to check a string like ({[]}), a stack solves it in one pass. Every output below comes from a real run on Python 3.10.
The core idea
Scan left to right. Push every opening bracket on a stack. On a closing bracket, pop and check that it matches. Only two things can go wrong: the closing bracket does not match the top of the stack, or openings are left over at the end.
Example 1: the checker
def is_valid(s):
pair = {')': '(', ']': '[', '}': '{'}
stack = []
for ch in s:
if ch in '([{':
stack.append(ch)
elif ch in ')]}':
if not stack or stack.pop() != pair[ch]:
return False
return stack == []
print(is_valid("({[]})"))
print(is_valid("(]"))
print(is_valid("([)]"))
print(is_valid("((("))
print(is_valid(""))
Output:
True
False
False
False
True
An empty string is balanced: nothing is left unmatched.
Example 2: watch "([)]" fail
Counting brackets is not enough. This string has one of each, and it is still wrong:
def trace(s):
pair = {')': '(', ']': '[', '}': '{'}
stack = []
for i, ch in enumerate(s):
if ch in '([{':
stack.append(ch)
print(f"step {i}: push {ch} stack={stack}")
else:
if not stack or stack[-1] != pair[ch]:
print(f"step {i}: '{ch}' needs '{pair[ch]}' on top, stack={stack} -> FAIL")
return
stack.pop()
print(f"step {i}: pop {ch} stack={stack}")
print("balanced" if not stack else f"unclosed left: {stack}")
trace("([)]")
Output:
step 0: push ( stack=['(']
step 1: push [ stack=['(', '[']
step 2: ')' needs '(' on top, stack=['(', '['] -> FAIL
The ) needs ( on top, but [ is in the way. The stack catches the order, not just the count.
Example 3: minimum brackets to add
For strings with only round brackets, count instead of using a real stack:
def min_adds(s):
opens = adds = 0
for ch in s:
if ch == '(':
opens += 1
elif opens:
opens -= 1
else:
adds += 1
return opens + adds
print(min_adds("())"))
print(min_adds("((("))
print(min_adds("()))(("))
Output:
1
3
4
Example 4: minimum removals to fix a string
Track indices, then drop every bracket that never matched:
def min_removals(s):
stack = [] # indices of '(' waiting for a match
drop = set() # indices to remove
for i, ch in enumerate(s):
if ch == '(':
stack.append(i)
elif ch == ')':
if stack:
stack.pop()
else:
drop.add(i)
drop.update(stack) # '(' that never matched
return ''.join(ch for i, ch in enumerate(s) if i not in drop)
print(repr(min_removals("a)b(c)d")))
print(repr(min_removals("(a(b)c")))
print(repr(min_removals(")(")))
Output:
'ab(c)d'
'a(b)c'
''
Example 5: longest valid substring
A classic interview follow-up. Keep the index before the current valid run at the bottom of the stack:
def longest_valid(s):
stack = [-1]
best = 0
for i, ch in enumerate(s):
if ch == '(':
stack.append(i)
else:
stack.pop()
if stack:
best = max(best, i - stack[-1])
else:
stack.append(i)
return best
print(longest_valid("(()"))
print(longest_valid(")()())"))
print(longest_valid(""))
Output:
2
4
0
When to reach for a stack
- Any "is this nested correctly" question: brackets, tags, undo history, function calls.
- Counting alone never works:
([)]has equal counts and is still wrong. - One pass is enough: every example here is O(n) time and O(n) worst-case space.
More stack practice (postfix evaluation, circular queues) in DSA in 30 Days, Episode 4: https://techvaniai.blogspot.com/2026/10/dsa-04-stack-postfix-evaluation-circular-queue.html
Python docs on using lists as stacks: https://docs.python.org/3/tutorial/datastructures.html#using-lists-as-stacks
Top comments (0)