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