What Are Balanced Parentheses?
A sequence of parentheses or other delimiters is balanced when every opening symbol has a corresponding closing symbol and the symbols are closed in the correct order.
The concept applies not only to parentheses (), but also to square brackets
[] and curly braces {}. Balanced delimiter checking is an important
application of the stack data structure.
Example: {[()]} is balanced because each closing symbol matches
the most recently opened symbol.
Balanced vs Unbalanced Expressions
An expression is balanced when all opening delimiters are eventually closed and their nesting order is correct.
| Expression | Result | Reason |
|---|---|---|
() |
Balanced | Matching pair |
{[]} |
Balanced | Correct nesting |
{[()]} |
Balanced | All symbols close in reverse order |
(] |
Unbalanced | Closing symbol does not match |
(( |
Unbalanced | An opening symbol remains |
) |
Unbalanced | Closing symbol appears without an opening symbol |
Why Is a Stack the Right Data Structure?
The important property of balanced parentheses is nesting. When multiple opening symbols are encountered, the most recently opened symbol must be closed first.
This is exactly the behavior provided by a stack:
- Push every opening delimiter onto the stack.
- When a closing delimiter appears, compare it with the stack's top element.
- If they match, remove the opening delimiter using
pop(). - If they do not match, the expression is invalid.
- After processing the complete expression, the stack must be empty.
Understanding the LIFO Relationship
Consider the expression:
{[()]}
The opening symbols are encountered in this order:
{is pushed.[is pushed.(is pushed.
The stack now contains:
[
"{",
"[",
"("
]
The next symbol is ). It must match the top element (.
After removing it, ] must match [, and finally } must match
{.
This reverse closing order is why the stack's LIFO behavior is a natural fit.
Algorithm for Balanced Parentheses
A general algorithm can support three types of delimiters: parentheses, square brackets, and curly braces.
- Create an empty stack.
- Scan the expression from left to right.
- If the current character is an opening delimiter, push it onto the stack.
- If it is a closing delimiter and the stack is empty, return false.
- Otherwise, pop the top opening delimiter.
- Check whether the popped delimiter matches the current closing delimiter.
- If any pair does not match, return false.
- After scanning all characters, return true only if the stack is empty.
JavaScript Implementation
The following implementation uses an object to map each closing delimiter to the opening delimiter it expects.
function isBalanced(expression) {
const stack = [];
const pairs = {
")": "(",
"]": "[",
"}": "{"
};
for (const char of expression) {
if (char === "(" || char === "[" || char === "{") {
stack.push(char);
} else if (char === ")" || char === "]" || char === "}") {
if (stack.length === 0) {
return false;
}
if (stack.pop() !== pairs[char]) {
return false;
}
}
}
return stack.length === 0;
}
console.log(isBalanced("{[()]}"));
// true
console.log(isBalanced("{[(])}"));
// false
Step-by-Step Example
Consider the expression:
({[]})
The algorithm processes each character in order:
| Character | Action | Stack |
|---|---|---|
( |
Push | ( |
{ |
Push | ( { |
[ |
Push | ( { [ |
] |
Pop [ |
( { |
} |
Pop { |
( |
) |
Pop ( |
Empty |
Because every closing delimiter matches the correct opening delimiter and the stack is empty at the end, the expression is balanced.
Handling Invalid Nesting
Matching individual characters is not enough. Their order also matters.
Consider:
([)]
There are two opening symbols, ( and [. Therefore, the next valid closing
symbol must be ]. Instead, the expression contains ).
The stack detects this immediately because its top element is [, which does not match
).
Handling Extra Closing Symbols
A closing delimiter cannot appear when there is no corresponding opening delimiter.
function isBalanced(expression) {
const stack = [];
const pairs = {
")": "(",
"]": "[",
"}": "{"
};
for (const char of expression) {
if (char === "(" || char === "[" || char === "{") {
stack.push(char);
continue;
}
if (char === ")" || char === "]" || char === "}") {
if (stack.length === 0) {
return false;
}
if (stack.pop() !== pairs[char]) {
return false;
}
}
}
return stack.length === 0;
}
console.log(isBalanced(")"));
// false
Handling Missing Closing Symbols
An expression can also be invalid when one or more opening delimiters remain in the stack after the entire expression has been processed.
console.log(isBalanced("((a + b)"));
// false
console.log(isBalanced("{[()]"));
// false
This is why the final stack.length === 0 check is important.
Ignoring Non-Delimiter Characters
In many practical problems, the expression contains letters, numbers, spaces, and operators in addition to delimiters. These characters do not affect the balance check and can simply be ignored.
console.log(
isBalanced("function test(a, b) { return (a + b); }")
);
// true
The algorithm only needs to react to opening and closing delimiters.
Time and Space Complexity
Let n represent the number of characters in the expression.
Each character is processed at most once.
| Metric | Complexity | Reason |
|---|---|---|
| Time | O(n) | Each character is examined once |
| Space | O(n) | The stack can contain up to n opening delimiters |
A More General Delimiter Matcher
Instead of checking each closing delimiter with separate conditions, the matching relationship can be stored in a map. This makes the implementation easier to extend if additional delimiter types are required.
function checkDelimiters(text) {
const stack = [];
const opening = new Set(["(", "[", "{"]);
const closingToOpening = new Map([
[")", "("],
["]", "["],
["}", "{"]
]);
for (const char of text) {
if (opening.has(char)) {
stack.push(char);
continue;
}
if (closingToOpening.has(char)) {
if (stack.length === 0) {
return false;
}
const expectedOpening = closingToOpening.get(char);
if (stack.pop() !== expectedOpening) {
return false;
}
}
}
return stack.length === 0;
}
console.log(checkDelimiters("[{()}]"));
// true
Where Balanced Parentheses Checking Is Used
Balanced delimiter checking is more than a programming interview exercise. Similar logic is used when software needs to understand nested structures.
- Compilers: validating and parsing source-code structures.
- Code editors: identifying matching brackets while developers type.
- Expression parsers: processing mathematical expressions.
- Template engines: handling nested template syntax.
- Markup processing: validating nested structural elements.
- Configuration formats: checking nested object or block structures.
- Interview problems: demonstrating stack-based problem-solving skills.
Common Mistakes
- Checking only whether the number of opening and closing symbols is equal.
- Ignoring the order in which delimiters are closed.
- Calling
pop()when the stack is empty. - Forgetting to verify that the stack is empty after processing the expression.
- Assuming every closing delimiter matches any opening delimiter.
- Using repeated string operations that make the solution unnecessarily expensive.
Why Counting Parentheses Is Not Enough
A common incorrect approach is to count opening and closing symbols. Equal counts do not guarantee correct nesting.
([)]
This expression contains two opening and two closing symbols, but it is still invalid because the delimiters are closed in the wrong order. A stack checks both the count and the nesting relationship.
Balanced Parentheses as a Stack Pattern
The balanced-parentheses problem teaches a general pattern that appears in many other problems: store unresolved opening or previous states, then resolve the most recent one first.
Once this pattern becomes familiar, similar problems involving nested structures, previous states, expression parsing, and backtracking become easier to recognize.
Key Takeaways
- Balanced parentheses require correct matching and correct nesting order.
- A stack is ideal because delimiters follow a LIFO relationship.
- Opening delimiters are pushed onto the stack.
- Closing delimiters must match and remove the stack's top element.
- An empty stack at the end indicates that all opening delimiters were matched.
- Equal numbers of opening and closing symbols are not enough to prove balance.
- The standard stack-based solution runs in O(n) time.
- The additional space required is O(n) in the worst case.
- The same technique is useful in parsers, code editors, expression evaluators, and nested-data processing.