Skip to main content
Data Structures & Algorithms · Chapters

ALGORITHMS / FOUNDATION

Stacks, queues and processing order

Use a stack to match nested structure, compare it with a queue, and make empty-state behaviour explicit.

4 MIN READBY MYSMARTNOTESFREE TO READ
Reading text size
BY THE END

Use last-in-first-out order to validate nested delimiters and explain why a FIFO queue cannot replace it.

On this pageMatch the order to the taskValidate nested delimitersThe stack is the unfinished structureCost follows the operationsMake empty behaviour part of the APIPractice: return a useful errorCheck your understandingSources

You open a parenthesis, then a square bracket. Which one must close first?

The most recent unmatched opening comes first. That small observation gives you the right data structure before you write a loop: a stack.

Stacks and queues are useful because they make an ordering rule explicit. The right rule can carry much of an algorithm’s reasoning for you.

Match the order to the task​

A stack is last-in, first-out. The newest pending item is handled next. A queue is first-in, first-out. The oldest pending item gets its turn.

A call stack, an undo history and nested delimiters often need the first rule. Breadth-first exploration and ordinary first-come work scheduling often need the second. A real scheduler may add priorities, fairness and cancellation, but it should still make its ordering policy clear.

The Open Data Structures linked-list discussion shows how a representation can support stack or queue operations. The abstract rule and the storage representation are separate choices.

Validate nested delimiters​

This example checks parentheses, square brackets and braces in a string. Other characters are ignored.

function balancedDelimiters(text) {
const openings = new Set(['(', '[', '{']);
const matching = new Map([[')', '('], [']', '['], ['}', '{']]);
const stack = [];

for (const symbol of text) {
if (openings.has(symbol)) {
stack.push(symbol);
} else if (matching.has(symbol)) {
if (stack.length === 0 || stack.pop() !== matching.get(symbol)) {
return false;
}
}
}
return stack.length === 0;
}

For ([{}]), each closing symbol matches the current top of the stack. For ([)], the closing parenthesis arrives while the top is a square bracket, so the function rejects it immediately.

This is not a complete programming-language parser. A bracket inside a quoted string or a comment may have different meaning. A real parser needs tokenisation rules that establish which symbols are syntax before applying this structural check.

The stack is the unfinished structure​

After reading any prefix of the input, the stack contains exactly its unmatched opening delimiters, in encounter order. The top is the opening that the next closing delimiter must match.

An opening extends that unfinished structure. A matching closing symbol completes its innermost part. A mismatch means no later symbol can repair the already-invalid nesting under this grammar.

At the end, a nonempty stack means some openings never closed. Returning true just because no mismatch occurred inside the loop would miss that case.

Cost follows the operations​

Each symbol is read once. An opening is pushed once and popped at most once. Under the ordinary dynamic-array stack model, total work is linear in input length. The stack can grow to linear size for input containing only openings.

If you need a FIFO queue in JavaScript, a head index into an array avoids repeatedly shifting the remaining contents. A long-lived queue also needs a policy for reclaiming processed storage; retaining every dequeued entry forever is a memory leak in slow motion.

Other languages provide dedicated structures. For example, Python documents deque with efficient operations at either end. Read the library’s contract rather than assuming every sequence type has the same removal costs.

Make empty behaviour part of the API​

For a custom stack or queue, what happens when it is empty? Throwing, returning a tagged result, and returning a special sentinel can all be reasonable. Returning a value that could also be a legitimate item creates ambiguity.

The delimiter checker avoids that ambiguity by inspecting the stack length before popping. The algorithm can then distinguish an unmatched closing delimiter from a valid comparison.

Practice: return a useful error​

Extend the checker to return the index of the first mismatch and the expected closing delimiter. What would you store on the stack?

Work through the approach

Store each opening delimiter with its input position. When a mismatch occurs, the top tells you which opening remains unfinished and which closing symbol was expected. An empty stack means the closing symbol has no matching opening.

At end of input, report an unclosed opening from the stack. If your positions must match a browser text editor, define whether they count code units, code points or another unit before returning them.

A QUICK CHECK

Does the idea hold?

When a closing bracket arrives, which opening bracket must it match?

SOURCES & FURTHER READING

References for the concepts and guarantees discussed in this chapter.

How these guides are made

  1. Open Data Structures: singly linked lists ↗
  2. Python: deque operations ↗