Valid Parentheses (Balanced Brackets)
What you'll practice:
Matching every opener to its closer with a stack of expected closers Failing fast on the first closer that does not match Stating the contract for empty input, other characters and non-strings before coding
Expected behavior
Your isValidParentheses() implementation should:
Return true when every opener is closed by the same kind of bracket in the right order Return false for a closer of the wrong kind, such as "(]", or a wrong closing order, such as "([)]" Return false when an opener is never closed or a closer arrives with nothing open Treat an empty string as valid Ignore every character that is not one of ( ) [ ] { } Throw a TypeError when the input is not a string
Arguments
s: string —The text to check. Only ( ) [ ] { } count; every other character is ignored.
Returns
boolean — Solution idea
Interview answer: scan the string once and keep a stack of the closers you still expect. Each opener pushes the closer it needs; each closer pops the top of the stack and must equal it. A mismatch, or a closer that finds the stack empty, means the string is invalid, and the string is valid only when the stack is empty at the end. Other characters are skipped. Time O(n), memory O(n) in the worst case of all openers.
isValidParentheses('()[]{}'); // => true
isValidParentheses('{[()]}'); // => true
isValidParentheses('([)]'); // => false (closed in the wrong order)
isValidParentheses('fn(a[0], { b: 1 })'); // => true (other characters are ignored)
isValidParentheses('('); // => false (never closed)
isValidParentheses(''); // => truePublic interview reports
These public, self-reported interview accounts informed this practice version. The exercise is modelled on reported rounds, not copied from them, and the reports are not verified official questions or endorsements.
- Taro: Walmart front end developer interview, India, October 2020
This self-reported front end developer loop lists Valid Parentheses next to let, var and const. This drill is a practice version of that problem; the rule for non-bracket characters is ours.
- Hashnode: frontend interview experience at Walmart, SDE 3 UI, January 2024
The DSA round of this self-reported SDE 3 UI loop had three easy-medium problems; the second was Valid Parentheses.
- Medium: Walmart Global Tech Software Engineer 3 UI interview, November 2024
The DSA round of this self-reported Software Engineer 3 UI loop asked First Missing Positive and then Valid Parentheses.
Solution
Overview
Interview answer: scan the string once and keep a stack of the closers you still expect. Each opener pushes the closer it needs; each closer pops the top of the stack and must equal it. A mismatch, or a closer that finds the stack empty, means the string is invalid, and the string is valid only when the stack is empty at the end. Other characters are skipped. Time O(n), memory O(n) in the worst case of all openers.
Mental model: the most recent unmatched opener must close first, which is exactly last in, first out. Counting each bracket kind is the common wrong answer: it accepts "([)]".
Approach 1: Stack of expected closers
Check the type first. Then loop over the characters: for (, [ or { push the matching closer; for ), ] or } pop and compare, returning false on a mismatch (popping an empty array gives undefined, which never matches). Skip everything else. Return expected.length === 0 so unclosed openers fail. One pass, no recursion, so thousands of nested brackets are fine.
export default function isValidParentheses(s) {
if (typeof s !== 'string') {
throw new TypeError('isValidParentheses expects a string');
}
const closerFor = { '(': ')', '[': ']', '{': '}' };
const expected = [];
for (const ch of s) {
if (ch === '(' || ch === '[' || ch === '{') {
expected.push(closerFor[ch]);
} else if (ch === ')' || ch === ']' || ch === '}') {
if (expected.pop() !== ch) {
return false;
}
}
}
return expected.length === 0;
}
export default function isValidParentheses(s: string): boolean {
if (typeof s !== 'string') {
throw new TypeError('isValidParentheses expects a string');
}
const closerFor: Record<string, string> = { '(': ')', '[': ']', '{': '}' };
const expected: string[] = [];
for (const ch of s) {
if (ch === '(' || ch === '[' || ch === '{') {
expected.push(closerFor[ch]);
} else if (ch === ')' || ch === ']' || ch === '}') {
if (expected.pop() !== ch) {
return false;
}
}
}
return expected.length === 0;
}
Approach 2: Remove adjacent pairs until nothing changes
Strip every non-bracket character, then repeatedly delete (), [] and {} until a pass changes nothing. The string was valid exactly when nothing is left. It is short and easy to reason about, but every pass rescans the string, so deeply nested input costs O(n²) time.
export default function isValidParentheses(s) {
if (typeof s !== 'string') {
throw new TypeError('isValidParentheses expects a string');
}
let rest = s.replace(/[^()[\]{}]/g, '');
let before;
do {
before = rest;
rest = rest.replace(/\(\)|\[\]|\{\}/g, '');
} while (rest !== before);
return rest === '';
}
export default function isValidParentheses(s: string): boolean {
if (typeof s !== 'string') {
throw new TypeError('isValidParentheses expects a string');
}
let rest = s.replace(/[^()[\]{}]/g, '');
let before: string;
do {
before = rest;
rest = rest.replace(/\(\)|\[\]|\{\}/g, '');
} while (rest !== before);
return rest === '';
}
Notes & Pitfalls
- Counting openers and closers per kind accepts "([)]", which closes in the wrong order.
- Forgetting the final empty-stack check accepts "(" and "(()".
- Calling a stack method on an empty stack without handling it: pop() returns undefined, so compare instead of assuming a value.
- Recursing per nesting level can overflow the call stack on long inputs; a loop cannot.
- Empty string → true.
- Only non-bracket characters → true.
- A closer first, such as ")(" → false at the first character.
- Non-string input → TypeError before the loop.
- Using an array as a stack with push and pop.
- Mapping each opener to the closer it expects.
- Single-pass validation with an early return.
Common mistakes on this challenge
Keep a stack of the closers you still expect
The most recent unmatched opener has to close first, which is last in, first out. Counting each bracket kind cannot see the order, so it accepts "([)]".
- Push `)`, `]` or `}` when you read `(`, `[` or `{`.
- On a closer, pop and compare: a different value, or `undefined` from an empty stack, means the string is invalid.
- Skip every other character, and return `true` only when the stack is empty at the end.
Common interview follow-ups:
- How would you report the index of the first unmatched bracket instead of a boolean?
- How would you ignore brackets inside string literals or comments in a code snippet?
- How would you support angle brackets or custom pairs passed in as a map?
- What changes if the input is a stream that arrives in chunks?
Resources
Similar questions
Guides
Preparing for interviews? Use Frontend Coding Challenges first, then move into a concrete Study Plan before targeted Company Prep.
Built in public
The code behind FrontendAtlas lives on GitHub. A star helps other frontend engineers find it.