datarekha
Coding Patterns Easy Asked at AmazonAsked at GoogleAsked at Meta

Check whether a string of brackets is valid.

The short answer

Use a stack: push every opening bracket and pop when you see a closing bracket, checking that it matches the top. If the stack is empty at the end, the string is valid. This is the canonical stack problem — O(n) time, O(n) space.

How to think about it

This is the problem interviewers use to check whether you reach for a stack on reflex. The tell is in the word “valid”: a closing bracket must match the most recently opened one still waiting — last in, first out. The moment you hear “match the thing I opened most recently,” your hand should already be moving toward a stack, and saying that out loud is half the points.

The mechanism is small. Keep a stack of unmatched openers. Walk the string one character at a time. See an opener — (, [, { — and push it. See a closer and ask two questions: is the stack empty, or does its top fail to match this closer? Either way the string is broken, so return False. Otherwise pop the matched opener and move on. When the loop ends, the string is valid only if the stack is empty — anything left over is an opener nobody closed. A tiny dict mapping each closer to its opener keeps the matching honest.

A worked example

def is_valid(s):
    stack = []
    match = {')': '(', ']': '[', '}': '{'}
    for ch in s:
        if ch in '([{':
            stack.append(ch)                       # opener -> remember it
        else:
            if not stack or stack[-1] != match[ch]:  # guard BEFORE indexing
                return False
            stack.pop()                            # matched -> discharge it
    return len(stack) == 0                          # nothing left unclosed?

print(is_valid("()[]{}"))
print(is_valid("([)]"))
print(is_valid("{[]}"))
print(is_valid("("))
print(is_valid(""))

# Trace the stack on a nested case
def is_valid_verbose(s):
    stack = []
    match = {')': '(', ']': '[', '}': '{'}
    for ch in s:
        if ch in '([{':
            stack.append(ch)
        else:
            if not stack or stack[-1] != match[ch]:
                print(f"  ch={ch!r}  -> mismatch, return False")
                return False
            stack.pop()
        print(f"  ch={ch!r}  stack={stack}")
    return len(stack) == 0

print("Trace for '{[]}':")
print("result:", is_valid_verbose("{[]}"))
True
False
True
False
True
Trace for '{[]}':
  ch='{'  stack=['{']
  ch='['  stack=['{', '[']
  ch=']'  stack=['{']
  ch='}'  stack=[]
result: True

Two lines in the output earn their keep. "([)]" returns False because when the ) arrives the stack top is [, not ( — correct nesting is violated even though the bracket counts balance. And "(" returns False not inside the loop but at the very end: every iteration passed, yet the stack still holds one opener, so the final emptiness check is what catches it. The trace of "{[]}" shows the stack growing to ['{', '['] and then draining back to empty, which is exactly the shape of a valid string.

Keep practising

All Coding Patterns questions

Explore further

Skip to content