“Know how to solve every problem that has been solved.” “What I cannot create, I do not understand.” — Richard Feynman

Valid Parentheses

Interview Prep

Warm-upStackstackstrings

The problem

Given a string containing only the characters ()[]{}, decide whether the string is valid: every opening bracket must close with the matching kind, and they must close in the right order. Empty string is valid; "(]" is not; "([)]" is not.

Pattern: stack as LIFO matcher

Bracket matching is the textbook use case for a stack. Push every opener onto the stack; on a closer, pop the top and check that it's the matching opener. The LIFO ordering is exactly what "innermost bracket closes first" means.

Reach for a stack any time the problem has nested structure that must unwind in reverse order — function call frames, HTML/XML tags, expression evaluation, the parser's recursion-descent machinery, Dyck-language recognition. All the same shape.

Optimal: one pass with a stack

A dictionary maps each closer to its opener. Single pass over the string. Three failure modes to catch: stack is empty on a closer (closer with no opener), top doesn't match (mismatched kinds), and stack non-empty at the end (unclosed opener).

def is_valid(s: str) -> bool:
    pairs = {')': '(', ']': '[', '}': '{'}
    stack: list[str] = []
    for ch in s:
        if ch in '([{':
            stack.append(ch)
        else:  # closing bracket
            if not stack or stack.pop() != pairs[ch]:
                return False
    return not stack

Trace

A valid case:

s = "({[]})"

ch = '('   open   stack = ['(']
ch = '{'   open   stack = ['(', '{']
ch = '['   open   stack = ['(', '{', '[']
ch = ']'   close  pop '['  → matches pairs[']'] = '['   ✓   stack = ['(', '{']
ch = '}'   close  pop '{'  → matches pairs['}'] = '{'   ✓   stack = ['(']
ch = ')'   close  pop '('  → matches pairs[')'] = '('   ✓   stack = []

End of string, stack is empty → return True

And a failing case where the kinds don't nest correctly:

s = "([)]"

ch = '('   open   stack = ['(']
ch = '['   open   stack = ['(', '[']
ch = ')'   close  pop '[' → but pairs[')'] = '('   ✗   return False

Complexity

Variations worth knowing

A use case: Lisp

Now, where would we actually use this valid-parenthesis tool in production? The programming language Lisp is a great use case, because a Lisp program is made of nested parentheses: (+ 1 (* 2 3)). This makes the compiler pretty straightforward to write. Like many programs, a compiler is a combination of a few different tricks — and the front of a Lisp compiler is made of exactly two: this parenthesis-matching trick, plus some tree-building. Push on ( starts a new list, pop on ) closes it and attaches it to its parent, and the two invalid cases of the interview problem become the only two syntax errors the reader can raise:

def read(src):
    tokens = src.replace('(', ' ( ').replace(')', ' ) ').split()
    stack = [[]]
    for t in tokens:
        if t == '(':
            stack.append([])                 # push: begin a new list
        elif t == ')':
            if len(stack) == 1:
                raise SyntaxError("unexpected )")   # pop of empty stack
            done = stack.pop()               # pop: close the current list
            stack[-1].append(done)
        else:
            stack[-1].append(t)
    if len(stack) != 1:
        raise SyntaxError("unclosed (")      # stack nonempty at the end
    return stack[0]

read("(+ 1 (* 2 3))")   # -> [['+', '1', ['*', '2', '3']]]
read("(+ 1 (* 2 3)")    # SyntaxError: unclosed (
read("(+ 1) )")         # SyntaxError: unexpected )

Fifteen lines, and it is a real parser — because matched parentheses are the entirety of Lisp's surface syntax. Programs are written directly as the tree the parser would have built anyway, which is what makes Lisp macros possible and Lisp parsers trivial. And once you see a compiler built like this, it's almost like music: swap out a few notes here and there and all of a sudden you're improvising — change what happens on push and pop and the same two tricks read JSON, s-expressions, or your own little language.