Senior 7 min · March 05, 2026

Balanced Parentheses — Why Counters Fail on ([)] Patterns

Counter-based bracket checkers accept '([)]' as valid, breaking JSX silently.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Uses a stack to match opening and closing brackets in order
  • Push on opener, check & pop on closer — fail fast on mismatch
  • Must check stack is empty at the end — not optional
  • O(n) time, O(n) space worst-case
  • The empty string is balanced — don't special-case it
  • Biggest mistake: forgetting final isEmpty() check or using counter for mixed brackets
Plain-English First

Imagine you're reading a book and every time you open a bracket '(' you put a coin in a cup, and every time you close one ')' you take a coin out. If the cup is empty at the end and you never tried to take a coin from an empty cup, the brackets are balanced. That's literally the entire algorithm. The 'cup' is what programmers call a stack — a structure that remembers what you pushed in and hands it back in reverse order.

Every compiler, code editor, and browser in the world checks whether your brackets match up before it does anything else. When VS Code underlines a missing closing brace in red, or when Java throws a compilation error about unexpected tokens, it's running a version of the balanced parentheses check under the hood. This is not a toy problem — it's the gatekeeper that runs before your code ever executes.

The problem itself is simple to state: given a string of brackets like '({[]})' or '([)]', decide whether every opening bracket has a matching closing bracket in the right order. The tricky part is 'in the right order' — because '([)]' has the same number of openers and closers, yet it's wrong. Order matters, and that's exactly what a stack was designed to track.

By the end of this article you'll understand what a stack is and why it's the perfect tool here, you'll be able to write a complete Java solution from memory, you'll know the two mistakes that trip up nearly every beginner, and you'll be ready to answer the three versions of this question that interviewers love to ask.

How Balanced Parentheses Works — Step by Step

The stack-based algorithm processes the string character by character:

  1. Create an empty stack. Define matching pairs: ')' matches '(', ']' matches '[', '}' matches '{'.
  2. For each character c in the string:
  3. a. If c is an opening bracket ('(', '[', '{'), push it onto the stack.
  4. b. If c is a closing bracket, check if the stack is empty. If empty, return False (no matching open).
  5. c. Otherwise, pop the top of stack and verify it is the correct opening bracket for c. If not, return False.
  6. After processing all characters, return True if the stack is empty (all opens were matched), False otherwise (unmatched opens remain).

For '()[]{}': push '(', pop-match ')' OK; push '[', pop-match ']' OK; push '{', pop-match '}' OK. Stack empty → True. For '([)]': push '(', push '['. See ')': pop '[', but '[' != '(' (wrong pair) → False. For '((': push '(', push '('. End: stack non-empty → False.

Production Insight
The crucial check is the final isEmpty(). In production code, forgetting this check means strings like '(((' pass validation silently.
Always return stack.isEmpty() — not true — after the loop.
That single line catches unmatched openers that slip through the loop.
Key Takeaway
Three rules: push openers, pop and match closers, return stack.isEmpty() at the end.
Fail fast on empty stack or mismatch.
All three are mandatory — skip any one and your validator is broken.

Worked Example — Tracing Nested Brackets

Check '{[()]}': step-by-step stack trace. 1. '{': push. Stack: ['{']. 2. '[': push. Stack: ['{','[']. 3. '(': push. Stack: ['{','[','(']. 4. ')': closing. Pop '(' — matches ')'. Stack: ['{','[']. 5. ']': closing. Pop '[' — matches ']'. Stack: ['{']. 6. '}': closing. Pop '{' — matches '}'. Stack: []. 7. Stack empty → balanced = True.

Check '({[}])': 1. push '(', push '{', push '['. 2. See '}': pop '[' — '[' does not match '}' → return False immediately.

Time complexity: O(n) — each character is processed once, push/pop are O(1). Space: O(n) for the stack in the worst case (all opening brackets).

Production Insight
Tracing manually exposes the exact point of failure. In production debugging, add log statements around push and pop to capture the stack state on each step.
The moment a mismatch occurs, you know exactly which bracket and which position caused it.
Use this trace technique when diagnosing broken linters or parsers.
Key Takeaway
Trace '({[}])' and see the mismatch at step 3: '[' vs '}'.
That immediate failure is what makes the stack approach O(n) and reliable.
No other data structure catches ordering errors this fast.

How Balanced Parentheses Works — Algorithm

Use a stack to check if opening brackets are matched and closed in the correct order.

Algorithm: 1. Create an empty stack and a matching map: ')':'(', '}':'{', ']':'['. 2. For each character c in the string: a. If c is an opening bracket ('(', '{', '['): push c. b. If c is a closing bracket: if stack is empty or stack.top != matching[c]: return False. Else pop. 3. Return True only if stack is empty at the end.

Worked example — '({[]})': '(': push. stack=['('] '{': push. stack=['(','{'] '[': push. stack=['(', '{', '['] ']': top='[', matches. pop. stack=['(','{'] '}': top='{', matches. pop. stack=['('] ')': top='(', matches. pop. stack=[] Stack empty. True.

Worked example — '([)]' (wrong order): '(': push. '[': push. ')': top='[', does NOT match '('. Return False.

Production Insight
Using a map for matching pairs makes the code cleaner and less error-prone than a chain of if-else. In production, a map also makes it easy to extend to other bracket types (e.g., '<' and '>') without touching logic.
But be careful: if the map is immutable and constructed once, it avoids accidental mutation in concurrent environments.
Always guard the pop with an isEmpty check — an EmptyStackException in production is a silent crash.
Key Takeaway
A matching map simplifies validation and scales to new bracket types.
The stack's top must match the expected closer — use the map, not nested ifs.
And always check isEmpty before pop — it's not optional.

What Is a Stack and Why Does It Fit This Problem Perfectly?

A stack is a collection with one rule: the last thing you put in is the first thing you get out. Programmers call this LIFO — Last In, First Out. Think of a stack of plates at a buffet. You add a clean plate to the top, and when someone takes a plate, they always grab from the top. Nobody digs from the bottom.

A stack supports three core operations. Push adds an item to the top. Pop removes and returns the item from the top. Peek looks at the top item without removing it. That's it. Simple, but powerful.

Now think about why this fits bracket matching perfectly. When you see an opening bracket like '(' or '{' or '[', you don't know yet what will close it — so you push it onto the stack and move on. When you later see a closing bracket like ')' or '}' or ']', the most recently opened bracket — sitting right on top of the stack — is the one that should match it. If it does match, pop it off and keep going. If it doesn't match, or the stack is empty when you expected something there, the string is unbalanced.

The stack's LIFO nature mirrors the natural nesting of brackets. The most recently opened bracket must be the first one closed. That's not a coincidence — stacks exist precisely to handle this kind of nested, ordered structure.

StackDemo.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package io.thecodeforge;

import java.util.Stack;

public class StackDemo {
    public static void main(String[] args) {

        // Java's built-in Stack class — we'll use this throughout the article
        Stack<Character> bracketStack = new Stack<>();

        // PUSH: add items to the top of the stack
        bracketStack.push('(');  // stack is now: ['(']
        bracketStack.push('[');  // stack is now: ['(', '[']
        bracketStack.push('{');  // stack is now: ['(', '[', '{']

        System.out.println("Stack after pushing three brackets: " + bracketStack);
        System.out.println("Top of stack (peek): " + bracketStack.peek()); // looks without removing

        // POP: remove and return the top item
        char topBracket = bracketStack.pop(); // removes '{' — the last thing we pushed
        System.out.println("Popped: " + topBracket);  // Output: {
        System.out.println("Stack after one pop: " + bracketStack); // ['(', '[']

        // isEmpty: crucial check before popping to avoid EmptyStackException
        System.out.println("Is stack empty? " + bracketStack.isEmpty()); // false
    }
}
Output
Stack after pushing three brackets: [(, [, {]
Top of stack (peek): {
Popped: {
Stack after one pop: [(, []
Is stack empty: false
Pro Tip:
Always call isEmpty() before calling pop() or peek(). If you call pop() on an empty stack, Java throws an EmptyStackException at runtime. In the balanced parentheses algorithm, an empty stack when you see a closing bracket means the string is immediately unbalanced — that's your signal to return false right away.
Production Insight
In production systems, stack usage for bracket validation is found in compilers, linters, and code formatters. The most common bug is forgetting isEmpty() before pop.
This leads to EmptyStackExceptions that crash the entire parsing pipeline.
Rule of thumb: every pop() must be preceded by an isEmpty() check, or at least be inside a try-catch that handles the exception gracefully.
Key Takeaway
Stack's LIFO is not a coincidence — it's the exact model for nested structures.
Push openers, pop and match closers.
Always guard pop with isEmpty() — an empty stack on closer means invalid input.

The Algorithm: Walking Through the Logic Step by Step

Here's the core insight stated plainly: scan the string character by character. If the character is an opening bracket ('(', '[', or '{'), push it onto the stack. If it's a closing bracket (')', ']', or '}'), check whether the top of the stack holds the matching opener. If yes, pop and continue. If no — or if the stack is empty — the string is unbalanced, stop and return false.

When you finish scanning every character, check whether the stack is empty. An empty stack means every opener found its closer. A non-empty stack means there are openers left hanging with no closers — also unbalanced.

Let's trace through '({[]})' manually before writing any code. - Read '(' → push. Stack: ['('] - Read '{' → push. Stack: ['(', '{'] - Read '[' → push. Stack: ['(', '{', '['] - Read ']' → top is '[', which matches ']'. Pop. Stack: ['(', '{'] - Read '}' → top is '{', which matches '}'. Pop. Stack: ['('] - Read ')' → top is '(', which matches ')'. Pop. Stack: [] - End of string, stack is empty → BALANCED.

Now trace '([)]'
  • Read '(' → push. Stack: ['(']
  • Read '[' → push. Stack: ['(', '[']
  • Read ')' → top is '[', which does NOT match ')'. → UNBALANCED. Stop immediately.

The mismatch detection happens in the middle of the string. That's what makes this algorithm elegant — it fails fast.

BalancedParenthesesChecker.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package io.thecodeforge;

import java.util.Stack;

public class BalancedParenthesesChecker {

    /**
     * Returns true if every opening bracket in the input string
     * has a correctly ordered, matching closing bracket.
     */
    public static boolean isBalanced(String inputString) {

        // Our stack holds opening brackets as we encounter them
        Stack<Character> openingBrackets = new Stack<>();

        // Walk through every character in the string one at a time
        for (int i = 0; i < inputString.length(); i++) {
            char currentChar = inputString.charAt(i);

            // --- CASE 1: It's an opening bracket → push and move on ---
            if (currentChar == '(' || currentChar == '[' || currentChar == '{') {
                openingBrackets.push(currentChar);

            // --- CASE 2: It's a closing bracket → check if it matches the top ---
            } else if (currentChar == ')' || currentChar == ']' || currentChar == '}') {

                // If the stack is empty, there's no opener to match this closer
                if (openingBrackets.isEmpty()) {
                    return false; // e.g. input starts with ')' — immediately wrong
                }

                // Look at the most recently pushed opener
                char mostRecentOpener = openingBrackets.pop();

                // Check whether the opener and closer are a valid pair
                boolean isMatchingPair =
                    (mostRecentOpener == '(' && currentChar == ')') ||
                    (mostRecentOpener == '[' && currentChar == ']') ||
                    (mostRecentOpener == '{' && currentChar == '}');

                if (!isMatchingPair) {
                    return false; // e.g. '([)]' — opener and closer don't match
                }

                // If they matched, the pop already removed the opener — carry on
            }
            // Any other character (letters, digits, spaces) is ignored
        }

        // After scanning everything, the stack must be empty.
        // If it's not, some openers never found their closers.
        return openingBrackets.isEmpty();
    }

    public static void main(String[] args) {
        String[] testCases = {
            "({[]})",   // all three bracket types, properly nested
            "(())",     // nested parentheses
            "([)]",     // wrong order — should fail
            "(((",      // openers with no closers — should fail
            ")))",      // closers with no openers — should fail
            "",         // empty string — technically balanced
            "{[()()]}", // complex valid nesting
            "({)}",     // looks close but order is wrong
        };

        for (String testCase : testCases) {
            String result = isBalanced(testCase) ? "BALANCED" : "UNBALANCED";
            // Padding the test case for clean console output
            System.out.printf("%-15s → %s%n",
                testCase.isEmpty() ? "(empty)" : testCase,
                result);
        }
    }
}
Output
({[]}) → BALANCED
(()) → BALANCED
([)] → UNBALANCED
((( → UNBALANCED
))) → UNBALANCED
(empty) → BALANCED
{[()()]} → BALANCED
({)} → UNBALANCED
Interview Gold:
Interviewers often ask 'what's the time complexity?' The answer is O(n) — you visit each character exactly once. Space complexity is O(n) in the worst case because you might push every character onto the stack (imagine a string of all opening brackets). Mention both without being asked and you'll stand out.
Production Insight
This implementation is production-ready for most use cases. But watch out for very long input strings: if the string is 10 million characters of all openers, the stack will consume ~80 MB (assuming a char per 2 bytes + stack overhead). That's fine on most servers, but in memory-constrained environments (e.g., Android, embedded), consider a counter-only approach if the input uses only one bracket type.
Also, the current implementation ignores non-bracket characters. That's correct for most linters, but if you need to parse a full expression (e.g., in a calculator), you'll need to track position and possibly report the exact index of the first mismatch. We'll cover that in an interview variation.
Key Takeaway
The code is simple: push on opener, pop and match on closer, check empty at end.
Fail fast on empty stack or mismatch.
Test with the exact edge cases: '([)]', '(((', ')))', and empty string.

Common Gotchas That Trip Up Nearly Every Beginner

Even after understanding the algorithm, there are two specific bugs that show up in beginner solutions so frequently they're almost a rite of passage. Let's look at them directly, see the broken code, and then see the fix.

The first gotcha is forgetting the final isEmpty() check. Many beginners return true as soon as the loop finishes without errors, not realising that unmatched openers sitting in the stack also make the string invalid.

The second gotcha is popping without checking whether the stack is empty first. If your input string starts with a closing bracket, the stack is empty when you try to pop — and Java throws an EmptyStackException that crashes your program instead of gracefully returning false.

There's also a subtler third mistake: using == to compare String objects instead of char values. This only applies if you accidentally store brackets as Strings rather than chars, but it produces silent wrong answers — one of the nastiest kinds of bugs.

The runnable examples below show each broken scenario and the one-line fix for each.

CommonMistakesDemo.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package io.thecodeforge;

import java.util.Stack;

public class CommonMistakesDemo {

    // ❌ BUGGY VERSION 1: Missing the final isEmpty() check
    public static boolean buggyNoEmptyCheck(String input) {
        Stack<Character> stack = new Stack<>();
        for (char ch : input.toCharArray()) {
            if (ch == '(' || ch == '[' || ch == '{') {
                stack.push(ch);
            } else if (ch == ')' || ch == ']' || ch == '}') {
                if (stack.isEmpty()) return false;
                stack.pop(); // simplified — ignoring mismatch for this demo
            }
        }
        return true; // ❌ BUG: returns true even when '(((' is the input!
        // Fix: return stack.isEmpty();
    }

    // ❌ BUGGY VERSION 2: Popping without checking isEmpty first
    public static boolean buggyNoEmptyGuard(String input) {
        Stack<Character> stack = new Stack<>();
        for (char ch : input.toCharArray()) {
            if (ch == '(' || ch == '[' || ch == '{') {
                stack.push(ch);
            } else if (ch == ')' || ch == ']' || ch == '}') {
                char top = stack.pop(); // ❌ BUG: crashes with EmptyStackException if input starts with ')'
                // Fix: if (stack.isEmpty()) return false;  ← add this line BEFORE calling pop()
            }
        }
        return stack.isEmpty();
    }

    public static void main(String[] args) {
        // Demonstrate Bug 1: '(((' has 3 openers and no closers
        System.out.println("Bug 1 — '(((' should be UNBALANCED:");
        System.out.println("Buggy result: " + buggyNoEmptyCheck("((("));  // prints true — WRONG

        // Demonstrate Bug 2: input starts with ')'
        System.out.println("\nBug 2 — ')))' should be UNBALANCED:");
        try {
            System.out.println("Buggy result: " + buggyNoEmptyGuard(")))"));
        } catch (java.util.EmptyStackException e) {
            System.out.println("CRASHED with EmptyStackException! Fix: check isEmpty() before pop()");
        }

        // Show the correct version handles both gracefully
        System.out.println("\nCorrect isBalanced('((('):  " + BalancedParenthesesChecker.isBalanced("((("));
        System.out.println("Correct isBalanced('))):  " + BalancedParenthesesChecker.isBalanced(")))"));
    }
}
Output
Bug 1 — '(((' should be UNBALANCED:
Buggy result: true
Bug 2 — ')))' should be UNBALANCED:
CRASHED with EmptyStackException! Fix: check isEmpty() before pop()
Correct isBalanced('((('): false
Correct isBalanced(')))'): false
Watch Out:
The empty string '' is a valid edge case — and it IS balanced, because there are zero openers and zero closers, so nothing is mismatched. Your algorithm handles it correctly by default: the loop never runs, and isEmpty() returns true. Don't add a special early-return for empty strings — you'll accidentally change the correct behaviour.
Production Insight
The two bugs shown here are the most common reasons why a production linter or code formatter fails. I've personally debugged a build pipeline where '(((' passed validation and caused a runtime error later in the compilation chain. The fix is always the same: return stack.isEmpty() and guard pop with isEmpty().
The third bug (String == comparison) is rarer but nastier — it passes tests with small strings because of string interning, then fails unpredictably with dynamic values. Always use char primitives or .equals() for String.
Key Takeaway
There are exactly two bugs that matter: missing final isEmpty() and missing guard before pop().
Both are one-line fixes.
If your code has these, it's wrong. Full stop.

Interview Prep: Variations Interviewers Add to the Core Problem

Once you've nailed the base algorithm, interviewers don't stop there. They mutate the problem to see if you truly understand it or just memorised the solution. Here are the three most common extensions, with the key insight for each.

Variation 1: 'What if the string contains non-bracket characters like letters and numbers?' The answer is: ignore them. Your loop only acts when a character is a bracket. Everything else passes through. The code already handles this — add any character to a test string and verify it yourself.

Variation 2: 'Can you solve this without a stack?' Yes, but only if the input uses a single type of bracket — say, only parentheses. In that case you can use a counter: increment for '(', decrement for ')'. If counter goes negative, return false. If counter is zero at the end, return true. This breaks immediately with multiple bracket types because you lose ordering information.

Variation 3: 'What's the minimum number of bracket additions or removals to make a string balanced?' This is a harder follow-up (LeetCode 921). The trick is to track two counters — open (unmatched openers) and close (unmatched closers) — and return their sum at the end. Knowing this follow-up exists and describing the approach impresses interviewers even if you don't code it live.

The code below implements the counter shortcut for single-bracket strings, so you can see where it works — and understand exactly why it breaks with mixed brackets.

SingleBracketCounterApproach.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package io.thecodeforge;

public class SingleBracketCounterApproach {

    /**
     * Counter-only approach — ONLY works correctly for a single bracket type.
     * Fails silently for mixed brackets like '([)]'.
     * Use this ONLY when you're certain the input has one bracket type.
     */
    public static boolean isBalancedSingleType(String input) {
        int openCount = 0; // tracks how many unmatched '(' we've seen so far

        for (char ch : input.toCharArray()) {
            if (ch == '(') {
                openCount++;       // found an opener — remember it
            } else if (ch == ')') {
                openCount--;       // found a closer — cancel one opener
                if (openCount < 0) {
                    return false;  // more closers than openers so far — can't recover
                }
            }
        }

        // openCount == 0 means all openers were matched
        return openCount == 0;
    }

    public static void main(String[] args) {
        // Works correctly for parentheses-only strings
        System.out.println("'(())' → " + isBalancedSingleType("(())"));    // true
        System.out.println("'())(' → " + isBalancedSingleType("())(" ));    // false
        System.out.println("'(((' → " + isBalancedSingleType("((("));       // false

        // ❌ Where the counter approach SILENTLY gives wrong answers
        // '([)]' has 2 openers and 2 closers, but they're in wrong order
        // Counter sees: +1, +1(ignored [), -1, -1(ignored ]) = 0 → reports BALANCED
        // But it's actually UNBALANCED — use the stack solution for mixed brackets!
        System.out.println("\n'([)]' counter result (WRONG): " + isBalancedSingleType("([)]"));
        System.out.println("'([)]' stack result  (CORRECT): " + BalancedParenthesesChecker.isBalanced("([)]"));
    }
}
Output
'(())' → true
'())(' → false
'(((' → false
'([)]' counter result (WRONG): true
'([)]' stack result (CORRECT): false
Interview Gold:
If an interviewer asks 'can you do this in O(1) space?', the honest answer for the general multi-bracket problem is no — you need the stack, which is O(n) space. But for single-bracket inputs the counter trick achieves O(1) space. Knowing when to use which solution — and why — is exactly what separates a junior answer from a senior one.
Production Insight
In production, you rarely know the input format in advance. A general parser must assume multiple bracket types. The counter shortcut is only safe if you control the input (e.g., a closed system where only parentheses are used). In a linter or compiler, always use the stack. The silent wrong answer from the counter is worse than an obvious crash — it leads to invalid code being accepted and causing runtime errors.
Key Takeaway
Stack is the general solution. Counter is a memory optimization for single-type inputs only.
Never use a counter for mixed brackets — it silently gives wrong answers.
If asked about O(1) space, mention the counter trick but explain its limitation.
● Production incidentPOST-MORTEMseverity: high

The Linter That Let Unmatched Brackets Through

Symptom
React app rendered blank white page with no console errors. Bundle compiled without warnings.
Assumption
The linter's bracket check was considered robust because it passed all unit tests with single-type brackets.
Root cause
The linter used a simple counter approach for all bracket types. In the JSX, a combination of curly braces and parentheses created a '([)]' pattern that the counter accepted as balanced. The actual JSX was syntactically invalid, but the linter never caught it.
Fix
Replaced the counter with a proper stack-based validator that checks matching pairs. Also added integration tests with mixed bracket types.
Key lesson
  • Never use a counter for multiple bracket types — it loses ordering information.
  • Always validate your bracket checker against edge cases like '([)]' and '(('.
  • Production linters should use a stack, not a simplified heuristic.
Production debug guideSymptom → Action guide for when your bracket checker gives wrong results4 entries
Symptom · 01
Your validator returns 'balanced' for '([)]'
Fix
Check if you're using a counter instead of a stack. Counter loses order. Switch to stack with explicit matching pairs.
Symptom · 02
EmptyStackException thrown at runtime
Fix
Your input started with a closing bracket. Add an isEmpty() guard before every pop(). If empty, return false immediately.
Symptom · 03
Unmatched openers like '(((' return 'balanced'
Fix
You're returning true at the end of the loop without checking if the stack is empty. Fix: return stack.isEmpty() instead of return true.
Symptom · 04
Validator fails on strings with non-bracket characters
Fix
Check if you're pushing non-bracket characters onto the stack. Your loop should only react to bracket characters; ignore everything else.
★ Quick Debug: Stack-Based Bracket CheckerWhen your bracket checker misbehaves, run these commands and checks to isolate the bug.
Wrong result for '([)]'
Immediate action
Print stack after each push and pop
Commands
Add System.out.println("Stack: " + stack) after each push/pop
Trace '([)]' manually and compare with printed output
Fix now
Ensure you're checking the top of stack against the matching pair, not just popping blindly
Crash on input starting with ')'+
Immediate action
Check if pop() is called on empty stack
Commands
Add if (stack.isEmpty()) return false; before every pop()
Add a debug print "Empty stack at index X" inside that guard
Fix now
Return false when stack is empty and a closing bracket is encountered
Empty string returns false+
Immediate action
Check return statement
Commands
Ensure the final return is stack.isEmpty() not return false
Add System.out.println("Final stack size: " + stack.size())
Fix now
Return stack.isEmpty() — empty string has zero openers, so stack is empty → true
Stack Approach vs Counter Approach
AspectStack Approach (General)Counter Approach (Single Bracket Only)
Supports multiple bracket typesYes — handles '(', '[', '{' togetherNo — gives wrong answers silently
Detects wrong ordering like '([)]'Yes — catches it immediatelyNo — counts match so it returns true incorrectly
Time ComplexityO(n) — one pass through the stringO(n) — one pass through the string
Space ComplexityO(n) — worst case all chars on stackO(1) — just one integer counter
Handles empty stringYes — returns true correctlyYes — returns true correctly
Fails fast on first mismatchYes — returns false immediatelyYes — returns false when count goes negative
Interview recommended?Always — it is the correct general solutionOnly mention as a follow-up optimization for limited inputs

Key takeaways

1
A stack's LIFO property is not accidental here
it directly mirrors bracket nesting, where the most recently opened bracket must always be the first one closed.
2
There are exactly two valid reasons to return false mid-loop
the stack is empty when you see a closing bracket, or the top of the stack doesn't match the closing bracket you just read.
3
The final isEmpty() check is not optional
a string full of unclosed openers like '(((' will pass the entire loop without error and needs that last check to be caught.
4
The counter shortcut (O(1) space) only works for single-bracket inputs
it silently gives wrong answers for mixed brackets because it loses all ordering information.

Common mistakes to avoid

3 patterns
×

Forgetting the final isEmpty() check

Symptom
String '(((' passes the loop without error because the code returns true unconditionally after the loop, so it incorrectly reports BALANCED.
Fix
Change the final return statement from 'return true;' to 'return stack.isEmpty();' — this ensures any unmatched openers are detected.
×

Calling pop() without first checking isEmpty()

Symptom
Input like ')))' has a closing bracket before any opener. When the code tries to pop from an empty stack, Java throws an EmptyStackException and crashes the program instead of returning false gracefully.
Fix
Add an 'if (stack.isEmpty()) return false;' guard immediately before every pop() call. This handles the case where a closer appears without any opener.
×

Using the counter trick for multi-bracket strings

Symptom
String '([)]' has equal numbers of openers and closers, so a counter reaches zero and reports BALANCED — but the order is wrong. This results in silent acceptance of invalid code in production.
Fix
Use the stack approach whenever the input can contain more than one type of bracket. Reserve the counter only for problems that are guaranteed to use a single bracket type (e.g., LeetCode 20 but only parentheses).
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
Given the string '{[()]}', walk me through your algorithm step by step —...
Q02SENIOR
What are the time and space complexities of your solution, and is there ...
Q03SENIOR
How would you modify your solution to also return the index of the first...
Q01 of 03JUNIOR

Given the string '{[()]}', walk me through your algorithm step by step — what's on the stack after each character?

ANSWER
Initialize empty stack. Character '{' -> push. Stack: ['{']. Character '[' -> push. Stack: ['{', '[']. Character '(' -> push. Stack: ['{', '[', '(']. Character ')' -> closing, pop top '(' matches ')', stack: ['{', '[']. Character ']' -> closing, pop top '[' matches ']', stack: ['{']. Character '}' -> closing, pop top '{' matches '}', stack empty. End of string, stack is empty -> balanced.
FAQ · 7 QUESTIONS

Frequently Asked Questions

01
What is the balanced parentheses problem in data structures?
02
Why do we use a stack to solve the balanced parentheses problem?
03
Is an empty string considered balanced?
04
What happens if we encounter a closing bracket when the stack is empty?
05
How do you count the minimum number of additions to make a string balanced?
06
Can this problem be solved without a stack?
07
What is the minimum number of bracket flips to make a string valid?
🔥

That's Stack & Queue. Mark it forged?

7 min read · try the examples if you haven't

Previous
Monotonic Stack Pattern
6 / 10 · Stack & Queue
Next
Next Greater Element Problem