Stack
A list where you only add and remove at the top, so the last item in is the first one out.
O(n)Space O(n)19 practice problemsStep-through pictureThe idea
A stack is a list with one open end, called the top. You can push an item onto the top, pop the top item off, or peek at it. You never reach into the middle. Because of this, the item you get back is always the most recent one you added: last in, first out.
That rule matches any task where the newest unfinished thing must be finished first. When you read ( [ ] ), the ] must close the [ that opened most recently, not the (. A stack remembers the open brackets, and its top is always the one that must close next.
In JavaScript a plain array is a good stack: push adds to the end, pop removes from the end, and stack[stack.length - 1] peeks. All three are O(1).
An everyday example
A pile of plates
In a kitchen, clean plates go on top of the pile, and whoever needs a plate takes the top one. Nobody pulls a plate from the middle. The plate you take is always the one that was put down last.
| In the example | In the code |
|---|---|
| The pile of plates | stack, an array |
| Putting a plate on top | stack.push(item) |
| Taking the top plate | stack.pop() |
| Looking at the top plate without taking it | stack[stack.length - 1] |
| No plates left | stack.length === 0 |
From the slow way to stack
- Start with the obvious check for balanced brackets: count them. Add one for every
(and subtract one for every), and the text is fine if the count never drops below zero and ends at zero. That works for one kind of bracket. With three kinds it fails:([)]has matching counts for every kind, yet the brackets cross each other. - Another obvious way is to delete pairs: find any
(),[], or{}that sit right next to each other, remove them, and repeat until nothing changes. If the text becomes empty, it was balanced. That is correct, but each pass scans the whole text, so it can takeO(n^2)time. - Look at what deleting pairs really does. A closer always pairs with the newest opener that is still open, because pairs nest: the inner pair must close before the outer one. So you never need the older openers until the newer ones are closed. You only ever need the most recent open bracket, and when it closes, the one before it becomes the most recent again.
- A list where you only add and remove at one end gives exactly that: the last item in is the first item out. Push each opener, and on each closer pop the top and check that it matches. At the end, the text is balanced only if nothing is left open. That loop is the stack-based bracket check.
How it works
- Create an empty array
stack. - Read the input one item at a time, here each
charoftext. - When something opens, push it:
stack.push(char). - When something closes, the matching opener must be on top. If the stack is empty, or
stack.pop()isn't the matching opener, the text is broken. - Ignore characters that neither open nor close.
- At the end, the text is balanced only if nothing is left open:
stack.length === 0.
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function isBalanced(text) {const pairs = { ")": "(", "]": "[", "}": "{" };const stack = [];for (const char of text) {if (char === "(" || char === "[" || char === "{") {stack.push(char);} else if (char in pairs) {if (stack.length === 0) return false;if (stack.pop() !== pairs[char]) return false;}}return stack.length === 0;}
How fast it is
| Time | O(n) |
|---|---|
| Extra memory | O(n) |
Each character is pushed and popped at most once, each in O(1). The stack can hold up to n openers.
- Best case
O(1)The first character is a closer, like
]abc: the stack is empty, so the function returnsfalseafter one character. - Average case
O(n)Ordinary text, balanced or with an error near the end. Each character is pushed and popped at most once.
- Worst case
O(n)A balanced text, which must be read to the last character before the answer is known, or a text of only openers like
((((.
Common mistakes
- Using
shiftandunshift, which work on the front of the array and move every other item. Usepushandpopon the end. - Popping or peeking an empty stack.
[].pop()returnsundefinedinstead of throwing, which hides the bug. Checkstack.lengthfirst. - Forgetting the final check that the stack is empty, so
((counts as balanced. - Counting brackets instead of using a stack. Counts can't tell
([)](broken) from([])(fine).
When to use it
Look for these clues in a problem:
- Things open and close in nested pairs: brackets, HTML tags, function calls, folders in a path.
- The most recent unfinished item must be handled first, like undo.
- The task processes a sequence left to right and sometimes cancels or combines the newest items, such as backspaces or collisions.
- A recursive solution exists and you want a loop instead: the call stack becomes your own stack.
Check yourself
Answer each one in your head first, then open it.
Why can't three counters, one per bracket kind, replace the stack?
Counters lose the order. ([)] and ([]) have the same counts, but only the second is balanced. The stack remembers which opener is newest.
What would go wrong without the final stack.length === 0 check?
Text with unclosed openers, like "((", would return true, because no closer ever failed.
How many pushes and pops does a balanced text of length n cause?
Exactly n / 2 pushes and n / 2 pops if every character is a bracket: one push per opener and one pop per closer.
Why is popping before checking the match safe?
If the popped item doesn't match, the function returns false right away, so it doesn't matter that the item was removed.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 19 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingeasy
medium
Go further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
The loop keeps one rule: as long as it hasn't returned, stack holds exactly the openers read so far that have not been closed yet, oldest at the bottom and newest on top. It starts empty, which is right for an empty prefix. Pushing an opener adds the newest open bracket on top, so the rule holds.
When a closer arrives, nesting means it must close the newest open bracket, the top. If the stack is empty, there is nothing for it to close, and no text added later can fix that, so false is right. If the top is the wrong kind, the pairs would cross, which also can never be fixed. If the top matches, popping it removes the one opener that just got closed, so the rule holds again.
The loop ends after the last character. By the rule, the stack then holds every opener that was never closed. The text is balanced only if that set is empty, which is exactly the final stack.length === 0 check.
Going deeper
3 more ideas
Why the top is always the right opener
In well-nested text, every pair closes before the pair around it does. So when a closer arrives, the opener it must match is the newest one still open. The stack keeps the openers in the order they arrived, and its top is the newest, which is exactly the one to check.
Cost
Each character is pushed at most once and popped at most once, and both are O(1), so a text of length n takes O(n) time. In the worst case, such as ((((((, every character is pushed, so the stack uses O(n) space.
Stacks and recursion
When a function calls itself, the engine pushes the call's local variables onto the call stack and pops them when it returns. Any recursive walk, such as a depth-first walk of the DOM, can be written as a loop with your own array as the stack. That avoids "Maximum call stack size exceeded" errors on very deep inputs.
Work it by hand
Every step in a table, with why
Check a short text that holds letters, two bracket pairs inside braces, and then one extra closer.
text = "{[a](b)}]"
| Step | char | Kind | stack after (bottom to top) | What happens | Why |
|---|---|---|---|---|---|
| 1 | { | opener | { | push | An opener can't be checked yet, so it waits on the stack. |
| 2 | [ | opener | { [ | push | The newest opener is now [, on top. |
| 3 | a | other | { [ | ignore | a is neither an opener nor a key of pairs, so the loop skips it. |
| 4 | ] | closer | { | pop [: match | pairs["]"] is [, and the popped top is [, so this pair is fine. |
| 5 | ( | opener | { ( | push | A new opener sits on {, which is still waiting. |
| 6 | b | other | { ( | ignore | Letters never touch the stack. |
| 7 | ) | closer | { | pop (: match | The newest open bracket is (, which is what ) needs. |
| 8 | } | closer | empty | pop {: match | The outer pair closes last, as nesting requires. The stack is empty. |
| 9 | ] | closer | empty | empty: return false | A closer arrives with nothing open, so stack.length === 0 and the function stops at once. |
It returns false at the ninth character, the extra ]. Without that last ], the loop would end with an empty stack and return true.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Stack | O(n) | O(n) | Several kinds of brackets, tags, or any nested pairs where the newest open item must close first. |
| One counter | O(n) | O(1) | Only one kind of bracket. The counter is the stack's height, since every item on the stack would be the same. |
| Repeatedly delete adjacent pairs | O(n²) | O(n) | Almost never. It is easy to explain, but each pass rescans the text. |
Extra memory: O(n) in the worst case: text made only of openers pushes every character. Text with no brackets at all uses O(1).
How the steps grow
- Stack, One counter
O(n) - Repeatedly delete adjacent pairs
O(n²)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Stack | 10 | 1,000 | 1 million |
| One counter | 10 | 1,000 | 1 million |
| Repeatedly delete adjacent pairs | 100 | 1 million | 1 trillion |
Inputs that need care
5 tricky inputs
- An empty string:
text = "" - The loop never runs, the stack is empty, and the function returns
true. Nothing opened means nothing is left open. - Only openers:
"(((" - Every character is pushed and nothing is popped. The final check sees three items and returns
false. - A closer with nothing open:
")(" - The first
)findsstack.length === 0and returnsfalsebefore the(is read. - Wrong kind:
"(]" ]pops(, butpairs["]"]is[, so it returnsfalse. A counter per kind would miss this.- Very deep nesting: 100,000
(then 100,000) - The array grows to 100,000 items and shrinks back, which is fine. A recursive checker could overflow the call stack here; a plain array stack doesn't.
Variations and follow-ups
Twists interviewers add
Report where the error is
Push the index along with the opener, as [char, i]. A closer with nothing open is an error at its own index. Anything left on the stack at the end points to the opener that was never closed, which is what an editor needs to underline.
Fewest insertions to balance one kind of bracket
With only ( and ), the stack holds nothing but (, so a counter can replace it. Count unmatched ) as you go, and add whatever ( remain at the end. With several kinds you need the real stack again.
Tag matching
Push tag names instead of characters. A closing tag </ul> must match the name on top. HTML parsers do this with their stack of open elements, and also use it to recover from missing closing tags.
Recursion as a loop
Any depth-first walk can push the things still to visit instead of calling itself. The array plays the role of the call stack, and the code no longer depends on the engine's stack size limit.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from "valid", "balanced", "matching", "nested", "undo", or "the most recent". Any time the newest unfinished thing must be finished first, think of a stack.
- Say the rule out loud: "the stack holds the openers that are still open, newest on top, so a closer must match the top." That one sentence explains both the push and the pop.
- Test by hand on an empty string,
")","((", and a crossed pair like"([)]". They hit the empty-stack check, the final check, and the mismatch check. - Expect the follow-up "can you do it in
O(1)space?". For one kind of bracket, yes, with a counter. For several kinds, no: you must remember the order of the open kinds, which can takeO(n)memory.
Where frontend code uses it
The same idea in real UI code
- The JavaScript engine's call stack is a stack: each function call pushes a frame, and an error's stack trace lists the frames from the top down.
- The HTML parser keeps a "stack of open elements" while it reads tags, and uses it to decide which element a closing tag closes.
- Editors use a stack to match brackets for highlighting and for bracket pair colorization, and undo history is a stack of past states.
- A depth-first walk of the DOM or a nested menu can use an explicit stack instead of recursion.