LeetCode Problem: #20 — Valid Parentheses
Difficulty: Easy
Topics: String, Stack
Pattern: Stack / Last-In-First-Out (LIFO)
Series: LeetCode Interview Preparation — From Beginner to Expert
Not every interview problem requires complex algorithms.
Some of the most frequently asked problems are built around a simple data structure that you must recognize quickly.
Valid Parentheses is one of them.
The problem looks simple:
()
[]
{}But it teaches one of the most important patterns in coding interviews:
Once you understand why a stack works here, you'll start recognizing the same pattern in problems involving:
Let's break it down.
Given a string containing only these characters:
(
)
{
}
[
]determine whether the input string is valid.
A string is valid if:
Input:
"()"
Output:
trueInput:
"()[]{}"
Output:
trueEvery opening bracket is correctly closed.
Input:
"(]"
Output:
falseWhy?
Because:
(
]do not match.
Input:
"([{}])"
Output:
trueThe brackets are properly nested.
Input:
"([)]"
Output:
falseThis is an important example.
We have:
(
[
)
]The ) tries to close ( while [ is still open.
The nesting order is incorrect.
How do we keep track of the most recent opening bracket?
Consider:
"([{}])"When we read the string:
(
([
([{
([{} At each point, the bracket we need to close is the most recently opened bracket.
That means:
Last opened
=
First one that must be closedThis is exactly the behavior of a:
A stack follows:
LIFOwhich means:
Last In, First Out
Think of a stack of plates.
If you put:
Plate A
Plate B
Plate Con top of each other, you remove:
C
B
AThe most recently added item comes out first.
That's exactly what nested parentheses require.
We'll maintain a stack of opening brackets.
When we encounter an opening bracket:
(
[
{we push it onto the stack.
When we encounter a closing bracket:
)
]
}we check the top of the stack.
If the top matches the closing bracket, remove it.
Otherwise:
invalidAt the end:
stack empty → valid
stack not empty → invalidConsider:
([{}])Start:
Stack: []Read:
(Push it:
Stack:
(Read:
[Push:
Stack:
[
(Read:
{Push:
Stack:
{
[
(Now we encounter:
}The top is:
{It matches.
Pop:
Stack:
[
(Next:
]Top is:
[Match.
Pop:
Stack:
(Next:
)Top is:
(Match.
Pop:
Stack:
[]The stack is empty.
Therefore:
ValidBefore jumping to a stack, let's think about what a naive solution might do.
One possible idea is repeatedly removing valid pairs:
()
[]
{}For example:
"([{}])"could become:
"([])"then:
"()"then:
""Therefore valid.
But repeatedly scanning and modifying the string can lead to poor performance and unnecessary complexity.
More importantly, it doesn't directly model the structure of the problem.
The stack does.
The problem has a specific relationship:
Opening brackets
↓
Nested structure
↓
Most recent opening bracket
↓
Must close first
↓
LIFO
↓
STACKThis is an excellent example of data-structure recognition.
You don't need a complicated algorithm.
You need to choose the right data structure.
def isValid(s):
stack = []
pairs = {
')': '(',
']': '[',
'}': '{'
}
for char in s:
if char in pairs:
if not stack or stack[-1] != pairs[char]:
return False
stack.pop()
else:
stack.append(char)
return len(stack) == 0This is the standard approach.
Let's break it down.
stack = []Python lists work well as stacks.
We use:
append()to push.
And:
pop()to remove the top element.
pairs = {
')': '(',
']': '[',
'}': '{'
}This lets us quickly determine which opening bracket should match a closing bracket.
For example:
pairs[')'] → '('
pairs[']'] → '['
pairs['}'] → '{'for char in s:For every character, we determine whether it's:
Opening bracketor:
Closing bracketif char in pairs:If the character is a closing bracket, we need to find its corresponding opening bracket.
First:
if not stack:
return FalseWhy?
Consider:
")"There is no opening bracket available to match it.
Therefore the string is invalid.
stack[-1] != pairs[char]The top of the stack must be the correct opening bracket.
For:
"([)]"when we reach:
)the stack is:
[
(Actually, the top is:
[But ) requires:
(Therefore:
[ != (and we return:
FalseIf the brackets match:
stack.pop()The opening bracket has now been successfully closed.
If the character isn't a closing bracket, it's an opening bracket.
We push it:
stack.append(char)At the end:
return len(stack) == 0Why?
Consider:
"((("There are three unmatched opening brackets.
The stack contains:
(
(
(Therefore:
stack != []and the string is invalid.
Input:
"()[]{}"(Stack:
[
(
])Top:
(Matches.
Pop.
Stack:
[][Push.
Stack:
[]Matches.
Pop.
Stack:
[]{Push.
}Matches.
Final stack:
[]Result:
TrueInput:
"([)]"Process:
(Stack:
(Then:
[Stack:
[
(Then:
)Expected:
(But top is:
[Mismatch.
Therefore:
FalseWe don't even need to process the remaining characters.
Input:
"((()))"Stack progression:
(
( (
( ( (
( (
(
[]More clearly:
Read ( → [(]
Read ( → [(, (]
Read ( → [(, (, (]
Read ) → [(, (]
Read ) → [(]
Read ) → []Final stack is empty.
Therefore:
TrueWe process every character once.
For each character, we perform constant-time operations:
append()
pop()
lookupTherefore:
O(n)where n is the length of the string.
In the worst case, all characters are opening brackets:
"((((((("The stack can contain n characters.
Therefore:
O(n)space.
No.
At minimum, we need to inspect the input.
For example, the invalid character could be at the very end:
"((((((((((((((((((]"We cannot know the answer without examining the relevant characters.
Therefore:
O(n)is optimal asymptotically.
The pattern isn't just:
parentheses → stackThe bigger pattern is:
When the most recently encountered item must be processed first, think Stack.
This applies to many problems.
Look for phrases such as:
Most recently opened
Most recently added
Most recent operationThink:
STACKFor example:
Type A
Type B
Type C
UndoThe most recent action is undone first.
That's LIFO.
Examples:
((()))
[({})]
HTML tags
Programming language syntaxNested structures often involve stacks.
This is another strong signal.
Problems asking you to remember previous elements that haven't been resolved yet often point toward stacks.
A common incorrect idea is:
Number of '(' == number of ')'That isn't enough.
Consider:
")("There is:
1 opening
1 closingbut the string is invalid.
Order matters.
That's why a stack is required.
Another mistake is checking whether the string contains valid pairs somewhere.
For example:
"([)]"contains:
()
[]but the nesting is wrong.
We need to validate the order.
Consider:
"]"If you do:
stack.pop()without checking whether the stack is empty, you'll get an error.
Always handle:
if not stack:
return FalseConsider:
"("Nothing mismatched during processing.
But the opening bracket was never closed.
Therefore the final stack must be empty.
A queue follows:
FIFOFirst In, First Out.
But nested brackets require:
LIFOLast In, First Out.
That's why a queue doesn't naturally solve this problem.
A Python list can act as a stack.
stack.append(value)stack[-1]stack.pop()if not stack:These four operations are worth memorizing.
You can also use a mapping from opening brackets to closing brackets:
def isValid(s):
stack = []
pairs = {
'(': ')',
'[': ']',
'{': '}'
}
for char in s:
if char in pairs:
stack.append(pairs[char])
else:
if not stack or stack.pop() != char:
return False
return not stackThis version has an interesting idea.
Instead of storing the opening bracket, we store the expected closing bracket.
For example:
(pushes:
)Then when we encounter:
)we simply compare it with the top.
Both approaches are valid.
Either is fine.
The first version is arguably easier to explain because the stack directly contains opening brackets:
(
[
{The second version is compact and elegant.
During an interview, prioritize:
Don't optimize for the fewest lines of code.
Once you've solved Valid Parentheses, interviewers can make the problem significantly harder.
Instead of checking whether parentheses are valid, find the length of the longest valid substring.
This leads to:
LeetCode #32 — Longest Valid Parentheses
How many parentheses must you add to make a string valid?
This tests whether you understand unmatched opening and closing brackets.
Remove the minimum number of invalid parentheses to make the expression valid.
This introduces more advanced:
BFS
DFS
BacktrackingDesign a stack that supports:
push()
pop()
top()
getMin()all efficiently.
This leads to:
LeetCode #155 — Min Stack
Use a stack to evaluate mathematical expressions.
This leads to:
LeetCode #150 — Evaluate Reverse Polish Notation
Stacks aren't just interview concepts.
They appear throughout software engineering.
When functions call other functions:
main()
↓
login()
↓
validate()
↓
database()the runtime maintains call-stack information.
Editors can maintain operations using stacks.
Action A
Action B
Action CUndo:
Cthen:
Bthen:
AHistory can be modeled using stack-like structures.
Compilers and interpreters use stack-based techniques to process expressions and nested structures.
Problems such as:
often rely on stack behavior either explicitly or through recursion.
This distinction is extremely important for interviews.
| Data Structure | Behavior | Typical Use |
|---|---|---|
| Stack | LIFO | Nested structures, undo, DFS |
| Queue | FIFO | BFS, scheduling, processing order |
Remember:
STACK
Last In → First OutQUEUE
First In → First OutWhenever you're unsure whether you need a stack, ask:
"If I encounter something now, will I need to resolve the most recent unresolved thing first?"
If yes:
Think Stack.For parentheses:
(
[ ← latest opening
{When a closing bracket appears, { must be handled first.
Exactly what a stack does.
If an interviewer asks you to explain your solution, you can structure your answer like this:
"I'll use a stack because brackets are nested and the most recently opened bracket must be closed first. I'll push every opening bracket onto the stack. When I encounter a closing bracket, I'll verify that the stack isn't empty and that its top matches the corresponding opening bracket. If it doesn't match, the string is invalid. After processing the entire string, the stack must be empty for the string to be valid. This takes O(n) time and O(n) space."
That's concise, technically accurate, and demonstrates that you understand why a stack is appropriate.
Our interview pattern library is growing.
Hash MapRunning Minimum
GreedyHash SetKadane's AlgorithmTwo PointersSorting
+
Two PointersStackNotice what we're building.
We're not simply collecting LeetCode solutions.
We're building a mental library of patterns.
Problem:
Validate parentheses/brackets.
Pattern:
Stack.
Why?
Most recently opened bracket
must be closed first.
Opening bracket:
push()
Closing bracket:
check stack top
If mismatch:
return False
If match:
pop()
At the end:
stack must be empty.
Time:
O(n)
Space:
O(n)Before moving forward, try solving these without looking at the solution.
Check:
"({[]})"Is it valid?
Check:
"([)]"Is it valid?
Explain exactly where it fails.
Check:
"((()))"Trace the stack after every character.
Check:
"{[("What does the final stack tell you?
Implement the solution without using a dictionary.
Design a stack supporting:
push
pop
peek
getMinin O(1) time.
LeetCode #20 looks like a simple parentheses problem.
But it teaches a fundamental interview skill:
Choose the data structure based on the behavior the problem requires.
Here, the requirement is:
Last opened
↓
First closedThat is:
LIFOAnd LIFO means:
STACKThe complete reasoning becomes:
Nested structure
↓
Most recent opening bracket
must be handled first
↓
LIFO
↓
Stack
↓
O(n) time
O(n) spaceOnce you recognize this pattern, a huge family of problems becomes easier.
The goal isn't to memorize:
stack.append()
stack.pop()The goal is to look at a problem and immediately ask:
"What behavior does my data structure need to provide?"
That's the kind of pattern recognition that turns LeetCode practice into actual interview preparation.
Pixels to Perfection Design that Impresses