Watch it run
Play it or step through. Every step says what just happened.
const pairs = { ")": "(", "]": "[", "}": "{" };function balanced(s) { const stack = []; for (const ch of s) { if ("([{".includes(ch)) stack.push(ch); else if (stack.pop() !== pairs[ch]) return false; } return stack.length === 0;}console.log(balanced("([]{})"));console.log(balanced("(]"));Characters still to read
The rest of the string, front first
Stack
Open brackets waiting to be closed; top is the newest
Matched
Pairs that closed correctly
Console0 lines
Nothing has run yet. The function walks the string once and keeps unmatched open brackets on a stack.
The same shape, a stack of things still open, is how parsers match tags and how your language's call stack tracks which function to return to.
The same shape, a stack of things still open, is how parsers match tags and how your language's call stack tracks which function to return to.
Try it yourself
Change the code, predict the order, then run it for real in your browser.
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.
Stacks
A stack needs two operations, push and pop, both at the same end. The end of a dynamic array is perfect: arr.push(x) and arr.pop() in JavaScript, list.append and list.pop() in Python, ArrayDeque.push and pop in Java (its docs say it is likely to be faster than the old Stack class). Your program's call stack is one: each call pushes a frame and each return pops it, which is why recursion and explicit stacks are interchangeable. Depth-first search, undo and redo, and evaluating expressions all use one.
Queues
A queue adds at the back and removes from the front. On a plain array, removing from the front is shift(), which moves every remaining element, so a busy queue becomes O(n) per operation. Two standard fixes:
- A ring buffer: a fixed array plus a head index and a count. Enqueue writes at
(head + size) % capacity, dequeue reads atheadand advances it, and both wrap around the end. Grow it like a dynamic array when it fills. This is howArrayDequeworks; Python'scollections.dequeuses linked blocks of elements for the same O(1) at both ends. - Two indexes into a growing array, compacting occasionally.
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
After two dequeues, "e" and "f" land in slots 0 and 1, the space freed at the front, and the order still comes out right.
Queues are also how systems absorb bursts: work arrives faster than it can be handled for a while, and the queue holds the difference. If the burst never ends, an unbounded queue just grows until memory runs out. A bounded queue that makes producers wait or rejects new work is the honest version, and it is the idea behind backpressure.