Balanced Parentheses Using Stack

Learn Balanced Parentheses Using Stack with practical examples, explanations, best practices, and real-world coding techniques.

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:

  1. Push every opening delimiter onto the stack.
  2. When a closing delimiter appears, compare it with the stack's top element.
  3. If they match, remove the opening delimiter using pop().
  4. If they do not match, the expression is invalid.
  5. 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.

  1. Create an empty stack.
  2. Scan the expression from left to right.
  3. If the current character is an opening delimiter, push it onto the stack.
  4. If it is a closing delimiter and the stack is empty, return false.
  5. Otherwise, pop the top opening delimiter.
  6. Check whether the popped delimiter matches the current closing delimiter.
  7. If any pair does not match, return false.
  8. 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.

Code Examples

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
    
function isBalanced(expression) {
  const stack = [];

  const pairs = {
    ")": "(",
    "]": "[",
    "}": "{"
  };

  for (const char of expression) {
    if ("([{".includes(char)) {
      stack.push(char);
      continue;
    }

    if (")]}".includes(char)) {
      if (stack.length === 0 || stack.pop() !== pairs[char]) {
        return false;
      }
    }
  }

  return stack.length === 0;
}

const expressions = [
  "()",
  "{[]}",
  "{[()]}",
  "([)]",
  "((a + b) * c)",
  "{[}"
];

for (const expression of expressions) {
  console.log(
    expression,
    "=>",
    isBalanced(expression)
  );
}
    
class Stack {
  constructor() {
    this.items = [];
  }

  push(value) {
    this.items.push(value);
  }

  pop() {
    return this.items.pop();
  }

  peek() {
    return this.items[this.items.length - 1];
  }

  isEmpty() {
    return this.items.length === 0;
  }
}

function isBalanced(expression) {
  const stack = new Stack();

  const pairs = {
    ")": "(",
    "]": "[",
    "}": "{"
  };

  for (const char of expression) {
    if ("([{".includes(char)) {
      stack.push(char);
      continue;
    }

    if (")]}".includes(char)) {
      if (stack.isEmpty()) {
        return false;
      }

      if (stack.pop() !== pairs[char]) {
        return false;
      }
    }
  }

  return stack.isEmpty();
}

console.log(isBalanced("[{()}]"));
// true
    

Pro Tip

Do not solve balanced-parentheses problems by counting opening and closing symbols alone. Correct nesting is the important part. Push opening delimiters onto a stack and require every closing delimiter to match the current stack top.

🎨 IgKnight Docs

Master HTML, CSS, JS, Python, .NET and more with easy-to-follow lessons and practical code examples.

© 2025 IgKnight Learning Platform. All rights reserved.