Stage 1: Foundations·Stack & Queue Fundamentals
Easy#205 min readStackLeetCode

Valid Parentheses

Stack matching

The stack’s ‘hello world’. Match most-recent open bracket.

Published Apr 15, 2026

Problem

Given a string s containing only the characters '(', ')', '{', '}', '[', ']', determine whether the input is balanced.

A string is balanced if every opening bracket is closed by the same type of bracket, and in the correct order.

Input
s = "()[]{}"
Output
true
Input
s = "(]"
Output
false
Explanation
A ( cannot be closed by ].
Input
s = "([)]"
Output
false
Explanation
The ( and ) are not properly nested around the [...].

Intuition

Brackets obey a last-in, first-out rule: the most recently opened bracket must be the first one closed. That's the exact semantics of a stack.

Push every opener. When you see a closer, pop the top and check it matches. If the stack is empty on a closer — or nonempty at the end — the string is unbalanced.

This generalizes to any nesting problem: function call frames, JSON parsing, HTML tag matching. Stack is the canonical "what's the most recent unresolved thing?" data structure.

Approach

Stack Matching

TimeO(n)
SpaceO(n)
Recommended

One pass. Push openers, pop + match on closers, verify empty at the end.

impl Solution {
    pub fn is_valid(s: String) -> bool {
        let mut stack: Vec<u8> = Vec::with_capacity(s.len());

        for &c in s.as_bytes() {
            match c {
                b'(' | b'[' | b'{' => stack.push(c),
                b')' => if stack.pop() != Some(b'(') { return false; },
                b']' => if stack.pop() != Some(b'[') { return false; },
                b'}' => if stack.pop() != Some(b'{') { return false; },
                _ => return false,
            }
        }

        stack.is_empty()
    }
}
Rust · runnable

Language notes:

  • Rust — Operating on bytes (s.as_bytes()) is safe because the alphabet is ASCII by definition. stack.pop() returns Option<u8>; comparing against Some(b'(') handles the "closer with empty stack" case uniformly.
  • TypeScript — A lookup Record<string, string> keeps the match map explicit. stack.pop() returns undefined on an empty stack, which never equals an opener — same uniform-comparison trick.

Trace

Input: "([])"

icactionstack after
0(push['(']
1[push['(', '[']
2]pop → '[' ✓['(']
3)pop → '(' ✓[]

Final: stack empty → true.

Edge cases

  • Empty string — trivially balanced; returns true.
  • Odd length — guaranteed unbalanced; micro-opt is a short-circuit s.length % 2 !== 0.
  • Closer with empty stack — handled by the "pop returns None/undefined ≠ opener" check.
  • Leftover openers — caught by the final is_empty() / length === 0 check.
💡
Tip

The monotonic-stack family (#739 Daily Temperatures, #84 Largest Rectangle) builds on the same "stack of unresolved things" pattern. Internalize this one first.

Takeaway

When order-of-operations matters and the relationship is last-opened-first-closed, reach for a stack. Scales from this one-screen problem all the way up to compilers and runtime systems.

More in Stack & Queue Fundamentals