Generate Parentheses (LeetCode 22)
Given n pairs of parentheses, produce every distinct combination of n '(' and n ')' characters that forms a well-formed string.
A string is well-formed when every '(' is eventually matched by a ')', and reading left to right, the running count of ')' never exceeds the running count of '('.
Worked Examples
n = 3["((()))","(()())","(())()","()(())","()()()"]( and three ).n = 1["()"]"()" is the only balanced string.⚖️Formal Constraints & Bounds
1 <= n <= 8
Core Insight
Dual counters constrain the search space exclusively to valid paths, achieving optimal O(Catalan(N)) generation.
Real-World Scenario & Production Applications
Compiler parser syntax validation, mathematical expression token generation.
Subproblems & Recurrence Decomposition3 Phases
Valid to add an opening bracket '(' whenever the count of open brackets open_count < n.
if open_count < n:
path.append("(")
dfs(open_count + 1, close_count)
path.pop()Step-by-Step Execution Trace Table
"" (open=0, close=0, d=0) | | choose '(' v "(" (open=1, close=0, d=1) / \ choose '(' / \ choose ')' / \ "((" (open=2, close=0, d=2) "()" (open=1, close=1, d=2) / \ | choose '(' / \ choose ')' | choose '(' (close==open) / \ v "(((" (open=3, close=0, d=3) "(()" (open=2, close=1, d=3) "()(" (open=2, close=1, d=3) | / \ / \ choose ')' | choose '(' / choose ')' \ choose '('/ choose ')' \ v / \ / \ "((()" (d=4) "(()(" (d=4) "(())" (d=4)"()((" (d=4) "()()" (d=4) | | | | | choose ')' | choose ')' | choose '(' |choose ')'| choose '(' | v v v v v "((())" (d=5) "(()()" (d=5) "(())(" (d=5)"()(()" (d=5) "()()(" (d=5) | | | | | choose ')' | choose ')' | choose ')' |choose ')'| choose ')' | v v v v v "((()))" ✅ "(()())" ✅ "(())()" ✅ "()(())" ✅ "()()()" ✅ Step-by-Step Decision Trace (for n = 3)
- Step 1 (Root, d=0):
path = ""(open=0, close=0). Rule:close < openis False (0 < 0), so must choose'('first. - Step 2 (Choose '(', d=1):
path = "("(open=1, close=0). Bothopen < 3andclose < openare True branch'('first. - Step 3 (Branch Left: Choose '(', d=2):
path = "(("(open=2, close=0).open < 3is True branch'('first. - Step 4 (Branch Left: Choose '(', d=3):
path = "((("(open=3, close=0).open == 3quota reached only legal choice is')'. - Step 5 (Choose ')', d=4):
path = "((()"(open=3, close=1).close < open(1 < 3) place')'. - Step 6 (Choose ')', d=5):
path = "((())"(open=3, close=2). Place')'. - Step 7 (Choose ')', d=6):
path = "((()))"(open=3, close=3). Base Case (len == 2*n = 6): Record"((()))"✅ (#1). - Step 8 (Backtrack / Pop ')', d=5):
path = "((())"↩️ BACKTRACK / POP. - Step 9 (Backtrack / Pop ')', d=4):
path = "((()"↩️ BACKTRACK / POP. - Step 10 (Backtrack / Pop ')', d=3):
path = "((("↩️ BACKTRACK / POP. - Step 11 (Backtrack / Pop '(', d=2):
path = "(("↩️ BACKTRACK / POP to explore right branch: choose')'. - Step 12 (Branch Right: Choose ')', d=3):
path = "(()"(open=2, close=1). Bothopen < 3andclose < openare True branch'('first. - Step 13 (Branch Left: Choose '(', d=4):
path = "(()("(open=3, close=1).open == 3quota reached place')'. - Step 14 (Choose ')', d=5):
path = "(()()"(open=3, close=2). Place')'. - Step 15 (Choose ')', d=6):
path = "(()())"(open=3, close=3). Base Case (len == 6): Record"(()())"✅ (#2). - Step 16 (Backtrack / Pop ')', d=5):
path = "(()()"↩️ BACKTRACK / POP. - Step 17 (Backtrack / Pop ')', d=4):
path = "(()("↩️ BACKTRACK / POP. - Step 18 (Backtrack / Pop '(', d=3):
path = "(()"↩️ BACKTRACK / POP to explore right branch: choose')'. - Step 19 (Branch Right: Choose ')', d=4):
path = "(())"(open=2, close=2).close == openonly legal choice is'('. - Step 20 (Choose '(', d=5):
path = "(())("(open=3, close=2).open == 3quota reached place')'. - Step 21 (Choose ')', d=6):
path = "(())()"(open=3, close=3). Base Case (len == 6): Record"(())()"✅ (#3). - Step 22 (Backtrack / Pop ')', d=5):
path = "(())("↩️ BACKTRACK / POP. - Step 23 (Backtrack / Pop '(', d=4):
path = "(())"↩️ BACKTRACK / POP. - Step 24 (Backtrack / Pop ')', d=3):
path = "(()"↩️ BACKTRACK / POP. - Step 25 (Backtrack / Pop ')', d=2):
path = "(("↩️ BACKTRACK / POP. - Step 26 (Backtrack / Pop '(', d=1):
path = "("↩️ BACKTRACK / POP to explore main right branch: choose')'. - Step 27 (Branch Right: Choose ')', d=2):
path = "()"(open=1, close=1).close == openonly legal choice is'('. - Step 28 (Choose '(', d=3):
path = "()("(open=2, close=1). Bothopen < 3andclose < openare True branch'('first. - Step 29 (Branch Left: Choose '(', d=4):
path = "()(("(open=3, close=1).open == 3quota reached place')'. - Step 30 (Choose ')', d=5):
path = "()(()"(open=3, close=2). Place')'. - Step 31 (Choose ')', d=6):
path = "()(())"(open=3, close=3). Base Case (len == 6): Record"()(())"✅ (#4). - Step 32 (Backtrack / Pop ')', d=5):
path = "()(()"↩️ BACKTRACK / POP. - Step 33 (Backtrack / Pop ')', d=4):
path = "()(("↩️ BACKTRACK / POP. - Step 34 (Backtrack / Pop '(', d=3):
path = "()("↩️ BACKTRACK / POP to explore right branch: choose')'. - Step 35 (Branch Right: Choose ')', d=4):
path = "()()"(open=2, close=2).close == openonly legal choice is'('. - Step 36 (Choose '(', d=5):
path = "()()("(open=3, close=2).open == 3quota reached place')'. - Step 37 (Choose ')', d=6):
path = "()()()"(open=3, close=3). Base Case (len == 6): Record"()()()"✅ (#5). - Step 38 (Backtrack / Pop ')', d=5):
path = "()()("↩️ BACKTRACK / POP. - Step 39 (Backtrack / Pop '(', d=4):
path = "()()"↩️ BACKTRACK / POP. - Step 40 (Backtrack / Pop ')', d=3):
path = "()("↩️ BACKTRACK / POP. - Step 41 (Backtrack / Pop '(', d=2):
path = "()"↩️ BACKTRACK / POP. - Step 42 (Backtrack / Pop ')', d=1):
path = "("↩️ BACKTRACK / POP. - Step 43 (Backtrack / Pop '(', d=0):
path = ""↩️ BACKTRACK / POP to Root. All 5 Catalan combinations generated! Output:["((()))", "(()())", "(())()", "()(())", "()()()"].
n = 3Expected:["((()))","(()())","(())()","()(())","()()()"]| 1 | results = [] |
| 2 | path = [] |
| 3 | def dfs(open_c, close_c): |
| 4 | if len(path) == 2 * n: |
| 5 | results.append(''.join(path)) |
| 6 | return |
| 7 | if open_c < n: |
| 8 | path.append('(') |
| 9 | dfs(open_c + 1, close_c) |
| 10 | path.pop() |
| 11 | if close_c < open_c: |
| 12 | path.append(')') |
| 13 | dfs(open_c, close_c + 1) |
| 14 | path.pop() |
| 15 | dfs(0, 0) |
| 16 | return results |
Conceptual Narrative
🧭 Conceptual Foundation & Pattern Intuition
The naive approach — generate every possible string of ( and ), then filter for validity — wastes enormous effort building strings that were doomed from their very first character. This archetype never builds an invalid string in the first place.
⚖️ The Analogy: A Balance Meter That Refuses Illegal Moves
Imagine building a tower out of "open" and "close" blocks, watched by a balance meter that goes up by one for every open block and down by one for every close block. Two rules keep the meter legal at every single step, not just at the end: you can only add an open block if you haven't used up your total budget of n yet, and you can only add a close block if the meter is currently above zero — otherwise you'd be closing something that was never opened.
🔑 The Key Idea in the Code
Notice there's no separate "is this string valid?" check anywhere — validity is enforced move-by-move, through the two if guards, so every single path that reaches the base case is automatically well-formed.
Adding close parenthesis when
close >= open: A closing parenthesis')'is only valid if strictly fewer closing parentheses than opening parentheses have been placed (close < open). Violating this produces invalid expressions.String immutability vs backtracking list: In Python, passing strings
current + '('creates new strings and requires no backtrack step. If using a mutable list, you must rememberpath.pop().Base case check: The base case triggers when
len(current) == 2 * n(bothopen == nandclose == n).Stagnant State in For-Loops. If you use a
for option in options:loop for fixed-choice problems, do not use a genericdfs(start + 1)variable. Because the loop options don't dynamically shrink, your state variables (likeopen_countandclose_count) must be passed into the recursive call, or you will trigger a Maximum Call Stack Exceeded error.
4-Phase Thought Process Model
Pattern Recognition Signals
The 10-second spot
Generate all valid sequences subject to structural open/close pairing constraints. Notice that recursive state is NOT a single 'start' index, but dual tracking variables (open_count, close_count).
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
State Representation & Invariant: State is polymorphic — here, it is the multi-variable tuple (open_count, close_count). At every decision step, close_count <= open_count <= n guarantees partial Catalan validity.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Assuming state is always a single 'start' index: In fixed-choice problems, state can be multiple tracking variables (open_count, close_count). Failing to pass these state counters into recursive calls leads to stagnant state.
Generating all 2^(2N) strings and filtering afterward: O(2^(2N)) vs optimal O(Catalan(N)).
Condition order: checking
close < open(notclose < n) ensures no unmatched closing brackets.
The 60-Second Interview Pitch
Say this out loud before you type a single line
"I maintain
open_countandclose_countas my polymorphic state. I add '(' whenopen_count < n, and add ')' whenclose_count < open_count. Whenlen(path) == 2 * n, I record the valid combination. This explores ONLY valid Catalan paths in O(4^N / sqrt(N)) time andO(N)stack space."Interview Tip: When under pressure, cognitive consistency is your best friend. Many engineers prefer to write out
for option in ["(", ")"]:explicitly. Even though it is mathematically just two branches, mapping it perfectly 1:1 with the universal Backtracking template eliminates the mental friction of switching paradigms.
Complexity & Mathematical Proof
O(4^N / sqrt(N))
Look at the execution steps: Number of valid parenthesizations of length 2N is the N-th Catalan number = 1/(N+1) * binom(2N, N) ~ 4^N / (N^(3/2) * sqrt(pi)). Constructing each string of length 2N takes O(N).
O(N) Call Stack
Recursion stack reaches depth 2N; path array stores 2N characters.
T(N) = CN · O(N) = O(4^N / √N)
Derivation Progression
Catalan Count ~ 4^N / (N^1.5 · √π)
Exact number of well-formed parentheses combinations.
open < N, close < open
Strictly prevents invalid prefix generation in O(1).
Total Time = O(4^N / √N)
Joins 2N characters per valid leaf.
Variable Definitions
Number of parentheses pairs (2N total characters in string)
N-th Catalan number = (1 / (N+1)) · (2N choose N) ~ 4^N / (N^1.5 · √π)
Memory Architecture & Bounds
O(N) Call stack depth 2N
O(N) Mutable character list
O( · N) Result string list
Boundary Best / Worst Cases
Decision & State-Space Search Tree
Senior SWE Deconstruction & Hardware Caveats
Triggers: "generate all combinations of well-formed parentheses", "given n pairs". Balanced count tracking: an opening parenthesis can be placed if ; a closing parenthesis can be placed if .
. Total valid combinations given by Catalan number .
Validating parentheses after building string (): pruning during construction reduces state space directly to Catalan numbers ().
Generate Parentheses (LeetCode 22)
Given n pairs of parentheses, produce every distinct combination of n '(' and n ')' characters that forms a well-formed string.
A string is well-formed when every '(' is eventually matched by a ')', and reading left to right, the running count of ')' never exceeds the running count of '('.
Worked Examples
n = 3["((()))","(()())","(())()","()(())","()()()"]( and three ).n = 1["()"]"()" is the only balanced string.⚖️Formal Constraints & Bounds
1 <= n <= 8
Core Insight
Dual counters constrain the search space exclusively to valid paths, achieving optimal O(Catalan(N)) generation.
Real-World Scenario & Production Applications
Compiler parser syntax validation, mathematical expression token generation.
Subproblems & Recurrence Decomposition3 Phases
Valid to add an opening bracket '(' whenever the count of open brackets open_count < n.
if open_count < n:
path.append("(")
dfs(open_count + 1, close_count)
path.pop()Step-by-Step Execution Trace Table
"" (open=0, close=0, d=0) | | choose '(' v "(" (open=1, close=0, d=1) / \ choose '(' / \ choose ')' / \ "((" (open=2, close=0, d=2) "()" (open=1, close=1, d=2) / \ | choose '(' / \ choose ')' | choose '(' (close==open) / \ v "(((" (open=3, close=0, d=3) "(()" (open=2, close=1, d=3) "()(" (open=2, close=1, d=3) | / \ / \ choose ')' | choose '(' / choose ')' \ choose '('/ choose ')' \ v / \ / \ "((()" (d=4) "(()(" (d=4) "(())" (d=4)"()((" (d=4) "()()" (d=4) | | | | | choose ')' | choose ')' | choose '(' |choose ')'| choose '(' | v v v v v "((())" (d=5) "(()()" (d=5) "(())(" (d=5)"()(()" (d=5) "()()(" (d=5) | | | | | choose ')' | choose ')' | choose ')' |choose ')'| choose ')' | v v v v v "((()))" ✅ "(()())" ✅ "(())()" ✅ "()(())" ✅ "()()()" ✅ Step-by-Step Decision Trace (for n = 3)
- Step 1 (Root, d=0):
path = ""(open=0, close=0). Rule:close < openis False (0 < 0), so must choose'('first. - Step 2 (Choose '(', d=1):
path = "("(open=1, close=0). Bothopen < 3andclose < openare True branch'('first. - Step 3 (Branch Left: Choose '(', d=2):
path = "(("(open=2, close=0).open < 3is True branch'('first. - Step 4 (Branch Left: Choose '(', d=3):
path = "((("(open=3, close=0).open == 3quota reached only legal choice is')'. - Step 5 (Choose ')', d=4):
path = "((()"(open=3, close=1).close < open(1 < 3) place')'. - Step 6 (Choose ')', d=5):
path = "((())"(open=3, close=2). Place')'. - Step 7 (Choose ')', d=6):
path = "((()))"(open=3, close=3). Base Case (len == 2*n = 6): Record"((()))"✅ (#1). - Step 8 (Backtrack / Pop ')', d=5):
path = "((())"↩️ BACKTRACK / POP. - Step 9 (Backtrack / Pop ')', d=4):
path = "((()"↩️ BACKTRACK / POP. - Step 10 (Backtrack / Pop ')', d=3):
path = "((("↩️ BACKTRACK / POP. - Step 11 (Backtrack / Pop '(', d=2):
path = "(("↩️ BACKTRACK / POP to explore right branch: choose')'. - Step 12 (Branch Right: Choose ')', d=3):
path = "(()"(open=2, close=1). Bothopen < 3andclose < openare True branch'('first. - Step 13 (Branch Left: Choose '(', d=4):
path = "(()("(open=3, close=1).open == 3quota reached place')'. - Step 14 (Choose ')', d=5):
path = "(()()"(open=3, close=2). Place')'. - Step 15 (Choose ')', d=6):
path = "(()())"(open=3, close=3). Base Case (len == 6): Record"(()())"✅ (#2). - Step 16 (Backtrack / Pop ')', d=5):
path = "(()()"↩️ BACKTRACK / POP. - Step 17 (Backtrack / Pop ')', d=4):
path = "(()("↩️ BACKTRACK / POP. - Step 18 (Backtrack / Pop '(', d=3):
path = "(()"↩️ BACKTRACK / POP to explore right branch: choose')'. - Step 19 (Branch Right: Choose ')', d=4):
path = "(())"(open=2, close=2).close == openonly legal choice is'('. - Step 20 (Choose '(', d=5):
path = "(())("(open=3, close=2).open == 3quota reached place')'. - Step 21 (Choose ')', d=6):
path = "(())()"(open=3, close=3). Base Case (len == 6): Record"(())()"✅ (#3). - Step 22 (Backtrack / Pop ')', d=5):
path = "(())("↩️ BACKTRACK / POP. - Step 23 (Backtrack / Pop '(', d=4):
path = "(())"↩️ BACKTRACK / POP. - Step 24 (Backtrack / Pop ')', d=3):
path = "(()"↩️ BACKTRACK / POP. - Step 25 (Backtrack / Pop ')', d=2):
path = "(("↩️ BACKTRACK / POP. - Step 26 (Backtrack / Pop '(', d=1):
path = "("↩️ BACKTRACK / POP to explore main right branch: choose')'. - Step 27 (Branch Right: Choose ')', d=2):
path = "()"(open=1, close=1).close == openonly legal choice is'('. - Step 28 (Choose '(', d=3):
path = "()("(open=2, close=1). Bothopen < 3andclose < openare True branch'('first. - Step 29 (Branch Left: Choose '(', d=4):
path = "()(("(open=3, close=1).open == 3quota reached place')'. - Step 30 (Choose ')', d=5):
path = "()(()"(open=3, close=2). Place')'. - Step 31 (Choose ')', d=6):
path = "()(())"(open=3, close=3). Base Case (len == 6): Record"()(())"✅ (#4). - Step 32 (Backtrack / Pop ')', d=5):
path = "()(()"↩️ BACKTRACK / POP. - Step 33 (Backtrack / Pop ')', d=4):
path = "()(("↩️ BACKTRACK / POP. - Step 34 (Backtrack / Pop '(', d=3):
path = "()("↩️ BACKTRACK / POP to explore right branch: choose')'. - Step 35 (Branch Right: Choose ')', d=4):
path = "()()"(open=2, close=2).close == openonly legal choice is'('. - Step 36 (Choose '(', d=5):
path = "()()("(open=3, close=2).open == 3quota reached place')'. - Step 37 (Choose ')', d=6):
path = "()()()"(open=3, close=3). Base Case (len == 6): Record"()()()"✅ (#5). - Step 38 (Backtrack / Pop ')', d=5):
path = "()()("↩️ BACKTRACK / POP. - Step 39 (Backtrack / Pop '(', d=4):
path = "()()"↩️ BACKTRACK / POP. - Step 40 (Backtrack / Pop ')', d=3):
path = "()("↩️ BACKTRACK / POP. - Step 41 (Backtrack / Pop '(', d=2):
path = "()"↩️ BACKTRACK / POP. - Step 42 (Backtrack / Pop ')', d=1):
path = "("↩️ BACKTRACK / POP. - Step 43 (Backtrack / Pop '(', d=0):
path = ""↩️ BACKTRACK / POP to Root. All 5 Catalan combinations generated! Output:["((()))", "(()())", "(())()", "()(())", "()()()"].
n = 3Expected:["((()))","(()())","(())()","()(())","()()()"]| 1 | results = [] |
| 2 | path = [] |
| 3 | def dfs(open_c, close_c): |
| 4 | if len(path) == 2 * n: |
| 5 | results.append(''.join(path)) |
| 6 | return |
| 7 | if open_c < n: |
| 8 | path.append('(') |
| 9 | dfs(open_c + 1, close_c) |
| 10 | path.pop() |
| 11 | if close_c < open_c: |
| 12 | path.append(')') |
| 13 | dfs(open_c, close_c + 1) |
| 14 | path.pop() |
| 15 | dfs(0, 0) |
| 16 | return results |
Conceptual Narrative
🧭 Conceptual Foundation & Pattern Intuition
The naive approach — generate every possible string of ( and ), then filter for validity — wastes enormous effort building strings that were doomed from their very first character. This archetype never builds an invalid string in the first place.
⚖️ The Analogy: A Balance Meter That Refuses Illegal Moves
Imagine building a tower out of "open" and "close" blocks, watched by a balance meter that goes up by one for every open block and down by one for every close block. Two rules keep the meter legal at every single step, not just at the end: you can only add an open block if you haven't used up your total budget of n yet, and you can only add a close block if the meter is currently above zero — otherwise you'd be closing something that was never opened.
🔑 The Key Idea in the Code
Notice there's no separate "is this string valid?" check anywhere — validity is enforced move-by-move, through the two if guards, so every single path that reaches the base case is automatically well-formed.
Adding close parenthesis when
close >= open: A closing parenthesis')'is only valid if strictly fewer closing parentheses than opening parentheses have been placed (close < open). Violating this produces invalid expressions.String immutability vs backtracking list: In Python, passing strings
current + '('creates new strings and requires no backtrack step. If using a mutable list, you must rememberpath.pop().Base case check: The base case triggers when
len(current) == 2 * n(bothopen == nandclose == n).Stagnant State in For-Loops. If you use a
for option in options:loop for fixed-choice problems, do not use a genericdfs(start + 1)variable. Because the loop options don't dynamically shrink, your state variables (likeopen_countandclose_count) must be passed into the recursive call, or you will trigger a Maximum Call Stack Exceeded error.
4-Phase Thought Process Model
Pattern Recognition Signals
The 10-second spot
Generate all valid sequences subject to structural open/close pairing constraints. Notice that recursive state is NOT a single 'start' index, but dual tracking variables (open_count, close_count).
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
State Representation & Invariant: State is polymorphic — here, it is the multi-variable tuple (open_count, close_count). At every decision step, close_count <= open_count <= n guarantees partial Catalan validity.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Assuming state is always a single 'start' index: In fixed-choice problems, state can be multiple tracking variables (open_count, close_count). Failing to pass these state counters into recursive calls leads to stagnant state.
Generating all 2^(2N) strings and filtering afterward: O(2^(2N)) vs optimal O(Catalan(N)).
Condition order: checking
close < open(notclose < n) ensures no unmatched closing brackets.
The 60-Second Interview Pitch
Say this out loud before you type a single line
"I maintain
open_countandclose_countas my polymorphic state. I add '(' whenopen_count < n, and add ')' whenclose_count < open_count. Whenlen(path) == 2 * n, I record the valid combination. This explores ONLY valid Catalan paths in O(4^N / sqrt(N)) time andO(N)stack space."Interview Tip: When under pressure, cognitive consistency is your best friend. Many engineers prefer to write out
for option in ["(", ")"]:explicitly. Even though it is mathematically just two branches, mapping it perfectly 1:1 with the universal Backtracking template eliminates the mental friction of switching paradigms.
Complexity & Mathematical Proof
O(4^N / sqrt(N))
Look at the execution steps: Number of valid parenthesizations of length 2N is the N-th Catalan number = 1/(N+1) * binom(2N, N) ~ 4^N / (N^(3/2) * sqrt(pi)). Constructing each string of length 2N takes O(N).
O(N) Call Stack
Recursion stack reaches depth 2N; path array stores 2N characters.
T(N) = CN · O(N) = O(4^N / √N)
Derivation Progression
Catalan Count ~ 4^N / (N^1.5 · √π)
Exact number of well-formed parentheses combinations.
open < N, close < open
Strictly prevents invalid prefix generation in O(1).
Total Time = O(4^N / √N)
Joins 2N characters per valid leaf.
Variable Definitions
Number of parentheses pairs (2N total characters in string)
N-th Catalan number = (1 / (N+1)) · (2N choose N) ~ 4^N / (N^1.5 · √π)
Memory Architecture & Bounds
O(N) Call stack depth 2N
O(N) Mutable character list
O( · N) Result string list
Boundary Best / Worst Cases
Decision & State-Space Search Tree
Senior SWE Deconstruction & Hardware Caveats
Triggers: "generate all combinations of well-formed parentheses", "given n pairs". Balanced count tracking: an opening parenthesis can be placed if ; a closing parenthesis can be placed if .
. Total valid combinations given by Catalan number .
Validating parentheses after building string (): pruning during construction reduces state space directly to Catalan numbers ().