Balanced Parentheses Problem

The Problem That Made Me Fall in Love With Stacks

Share
Balanced Parentheses Problem

Some problems, once you understand them, quietly change the way you think. For me, "check if the brackets are balanced" was one of them. It looks like a small toy problem, but it is the reason the stack data structure finally clicked in my head. And believe me, if you understand this one properly, you will start seeing stacks everywhere — in compilers, in text editors, in your own code.

So let us sit together and go through it slowly, the way I wish someone had explained it to me in the beginning.

The Problem

We are given a string containing brackets — round (), square [], and curly {}. We have to check whether they are balanced.

What does "balanced" actually mean? Two simple conditions:

  1. Every opening bracket must have a matching closing bracket.
  2. The brackets must close in the correct order — the one that opened last must close first.

Let us look at a few examples to build the feeling:

  • "()" → balanced ✅
  • "([])" → balanced ✅
  • "([)]" → not balanced ❌ (the order is wrong!)
  • "((" → not balanced ❌ (something is left open)

That third example is the important one. Notice that the count of opening and closing brackets is equal, but it is still wrong. This tells us something crucial — just counting brackets is not enough. The order matters. And whenever order matters like this, your mind should immediately whisper one word: stack.

The Core Idea

Think about how you yourself would check this on paper. As you read left to right:

  • Every time you see an opening bracket, you make a mental note: "I am now waiting for this one to close."
  • Every time you see a closing bracket, you check: "Does it match the most recent opening bracket I am still waiting for?"

That phrase — "the most recent one" — is the whole secret. The last bracket you opened is the first one that must close. Last in, first out. That is precisely what a stack gives you.

So the plan is:

  1. See an opening bracket → push it onto the stack.
  2. See a closing bracket → look at the top of the stack. If it is the matching opening bracket, pop it off. If not, the string is unbalanced.
  3. At the end, the stack must be empty. If something is still sitting there, it means a bracket was never closed.

The Implementation

Let us put this thinking into clean code.

function isBalanced(s) {
    let stack = [];

    // for each closing bracket, what opening bracket do we expect?
    let map = {
        ')': '(',
        ']': '[',
        '}': '{'
    };

    for (let ch of s) {
        if (ch === '(' || ch === '[' || ch === '{') {
            // opening bracket → remember it
            stack.push(ch);
        } else if (ch === ')' || ch === ']' || ch === '}') {
            // closing bracket → it must match the most recent opening one
            if (stack.length === 0 || stack.pop() !== map[ch]) {
                return false;
            }
        }
    }

    // if anything is left, some bracket was never closed
    return stack.length === 0;
}

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

Walking Through the Code

Let us not run away after pasting. Let us actually feel what is happening.

The map:

let map = {
    ')': '(',
    ']': '[',
    '}': '{'
};

This little object is our lookup table. When we meet a closing bracket, it instantly tells us which opening bracket we should find waiting on top of the stack. This keeps our if conditions clean instead of writing separate checks for each bracket type.

When we see an opening bracket:

We simply push it. We are saying, "Note taken, I will wait for you to close later."

When we see a closing bracket:

This is the heart of the logic:

if (stack.length === 0 || stack.pop() !== map[ch]) {
    return false;
}

Two things can go wrong here, and this single line catches both:

  • stack.length === 0 → a closing bracket arrived but there is nothing open to match it. Like getting a ) when nothing was ever opened. Wrong.
  • stack.pop() !== map[ch] → there is something open, but it is the wrong type. Like closing with ] when the last opening was (. Wrong.

Notice the short-circuit || — we check for the empty stack first, so that stack.pop() never runs on an empty stack. Small detail, but this is the kind of thing a good interviewer notices.

The final return:

return stack.length === 0;

If we survived the whole loop without returning false, we are almost done — but not quite. There is one last check. If the stack still has something inside, it means an opening bracket never got its partner. Only an empty stack at the end means truly balanced.

Let Us Trace It Once

Take "([)]" — the tricky one. Watch the stack:

CharacterActionStack after
(push[ ( ]
[push[ (, [ ]
)top is [, but we need ( → mismatch!return false

See how the stack caught the wrong ordering immediately? The counts were equal, but the order was broken — and the stack felt it at once. That is the beauty of it.

Complexity

  • Time: O(n) — we walk through the string exactly once, and each push/pop is O(1).
  • Space: O(n) in the worst case — imagine a string like "(((((", where every character is an opening bracket and the stack grows to the full length.

Very efficient. Nothing wasted.

Final Thoughts

This problem is small, but the lesson inside it is big: whenever the order of "most recent first" matters, a stack is your friend. Matching brackets, undo-redo in an editor, the back button in your browser, function calls in your program — all of them lean on this same last-in-first-out idea.

Understand this one problem deeply, and you have not just learned to match brackets — you have learned to think in stacks. And that, my friend, will take you a long way.

Happy coding, and all the best for your interviews!