Understanding Stacks: Concepts, Common Use Cases, and JavaScript Implementations
This article explains the stack data structure, its LIFO characteristics, typical applications such as bracket matching, expression evaluation, next‑greater‑element problems, browser navigation, and JavaScript execution contexts, and provides clear JavaScript code examples for each scenario.
A stack is a fundamental data structure that follows a Last‑In‑First‑Out (LIFO) principle, allowing insertion and removal of elements only at one end.
Typical characteristics: only one end supports push and pop operations, and the order is LIFO.
Common scenarios where a stack is useful include:
Bracket matching
Expression evaluation
Next greater element
Browser forward/backward navigation
JavaScript execution context stack
Bracket Matching
To verify whether a string containing parentheses, brackets, and braces is valid, scan the string from left to right, pushing left symbols onto a stack and popping when a matching right symbol is found. The string is valid if the stack is empty at the end.
function validString(string) {
const resultStack = [];
const markMap = new Map([
["(", ")"],
["[", "]"],
["{", "}"]
]);
const top = arr => arr[arr.length - 1];
for (let i = 0; i < string.split("").length; i++) {
const str = string[i];
const isLeft = markMap.get(str);
if (isLeft) {
resultStack.push(str);
continue;
}
const isRelative = str === markMap.get(top(resultStack));
if (!isRelative) {
return false;
}
resultStack.pop();
}
return !resultStack.length;
}
console.log(validString("[[({}{})]]({})")); // trueExpression Evaluation
Using two stacks—one for operands and one for operators—allows evaluation of arithmetic expressions respecting operator precedence.
const priorityMap = { "+": 0, "-": 0, "*": 1, "/": 1 };
const computeMap = {
"+": (a, b) => a + b,
"-": (a, b) => a - b,
"*": (a, b) => a * b,
"/": (a, b) => a / b,
};
const top = arr => arr[arr.length - 1];
const top2 = arr => arr[arr.length - 2];
const isEmpty = arr => !arr.length;
const compute = (numberStack, markStack) => {
const computeFn = computeMap[markStack[markStack.length - 1]];
const result = computeFn(top2(numberStack), top(numberStack));
markStack.pop();
numberStack.pop();
numberStack.pop();
numberStack.push(result);
};
function computeExpression(expressionString) {
const markStack = [];
const numberStack = [];
expressionString.split("").forEach(str => {
const isMark = priorityMap.hasOwnProperty(str);
if (!isMark) {
numberStack.push(str - 0);
return;
}
while (!isEmpty(markStack) && priorityMap[str] <= priorityMap[top(markStack)]) {
compute(numberStack, markStack);
}
markStack.push(str);
});
while (markStack.length) {
compute(numberStack, markStack);
}
const result = top(numberStack);
numberStack.pop();
return result;
}
console.log(computeExpression("1+2*5*1+4/2+5")); // 18Next Greater Element
Given an array, the task is to produce an array where each position holds the next greater element to its right, or -1 if none exists. A monotonic decreasing stack yields an O(n) solution.
const top = arr => arr[arr.length - 1];
const isEmpty = arr => !arr.length;
var nextGreaterElement = function(numbers) {
const stack = [];
let ans = [];
for (let i = numbers.length - 1; i >= 0; i--) {
const cur = numbers[i];
while (!isEmpty(stack) && cur >= top(stack)) {
stack.pop();
}
ans[i] = isEmpty(stack) ? -1 : top(stack);
stack.push(cur);
}
return ans;
};Daily Temperatures (Next Greater in Time)
For each day’s temperature, compute how many days must pass until a warmer temperature occurs; use a stack that stores indices.
var dailyTemperatures = function(numbers) {
const stack = [];
let ans = [];
for (let i = numbers.length - 1; i >= 0; i--) {
const cur = numbers[i];
while (!isEmpty(stack) && cur >= numbers[top(stack)]) {
stack.pop();
}
ans[i] = isEmpty(stack) ? 0 : top(stack) - i;
stack.push(i);
}
return ans;
};Circular Next Greater Element
When the array is considered circular, iterate twice over the array using modulo arithmetic to simulate the wrap‑around.
var nextGreaterElement = function(numbers) {
const stack = [];
let ans = [];
for (let i = numbers.length * 2 - 1; i >= 0; i--) {
const cur = numbers[i % numbers.length];
while (!isEmpty(stack) && cur >= top(stack)) {
stack.pop();
}
if (i < numbers.length) {
ans[i] = isEmpty(stack) ? -1 : top(stack);
}
stack.push(cur);
}
return ans;
};Browser Forward/Backward Navigation
Two stacks (back‑stack and forward‑stack) model the browser’s history: navigating to a new page pushes it onto the back‑stack; back moves the top page to the forward‑stack; forward moves it back; visiting a new page clears the forward‑stack.
JavaScript Execution Context Stack
JavaScript’s single‑threaded engine uses an execution context stack: the global context is pushed first, each function call creates a new context pushed on top, and contexts are popped when functions return.
var color = 'blue';
function changeColor() {
var anotherColor = 'red';
function swapColors() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
}
swapColors();
}
changeColor();These examples illustrate how the stack abstraction underlies many core programming tasks and system behaviors.
New Oriental Technology
Practical internet development experience, tech sharing, knowledge consolidation, and forward-thinking insights.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.