A stack in JavaScript is a linear data structure that follows the LIFO (Last In, First Out) principle. The element added most recently is the first element removed.
Stacks are useful whenever data needs to be processed in reverse order. Common examples include browser history, undo and redo functionality, function call management, expression evaluation, backtracking algorithms, and depth-first search.
Think of a stack of plates. You place a new plate on the top, and when you remove a plate, you take the one from the top first.
This is the basic idea behind LIFO: the last item added is the first item removed.
How a Stack Works
A stack generally exposes a small set of core operations:
- Push: Add an element to the top.
- Pop: Remove and return the top element.
- Peek: View the top element without removing it.
- isEmpty: Check whether the stack contains elements.
- Size: Determine the number of elements.
In a stack, insertion and deletion happen at the same end, commonly called the top.
Implementing a Stack with a JavaScript Array
JavaScript arrays provide push() and
pop(), making them a straightforward way to implement
a stack.
const stack = [];
stack.push("A");
stack.push("B");
stack.push("C");
console.log(stack);
const removed = stack.pop();
console.log("Removed:", removed);
console.log("Stack:", stack);
After pushing A, B, and C, the top of the stack is C. Calling
pop() therefore removes C first.
Push Operation
The push() method adds one or more elements to the end
of an array. When the end of the array represents the top of the stack, this becomes the
stack's push operation.
const stack = [];
stack.push(10);
stack.push(20);
stack.push(30);
console.log(stack);
The stack now contains [10, 20, 30], with
30 at the top.
Pop Operation
The pop() method removes and returns the last element.
This matches the LIFO behavior of a stack when the array's end is treated as the top.
const stack = [10, 20, 30];
const item = stack.pop();
console.log("Removed:", item);
console.log("Remaining:", stack);
If the stack is empty, pop() returns
undefined.
Peek at the Top Element
JavaScript arrays do not have a built-in method named peek().
The top element can be inspected using the last index or the
at() method.
const stack = [10, 20, 30];
const top = stack.at(-1);
console.log("Top:", top);
console.log("Stack:", stack);
The important difference is that peeking does not remove the element from the stack.
Checking Whether a Stack Is Empty
The length property can be used to determine whether
an array-based stack contains any elements.
const stack = [];
if (stack.length === 0) {
console.log("Stack is empty");
}
This check is particularly useful before accessing or removing the top element.
Stack Size
The number of elements in an array-based stack is available through the
length property.
const stack = ["HTML", "CSS", "JavaScript"];
console.log("Stack size:", stack.length);
Creating a Stack Class in JavaScript
For applications where stack behavior should be encapsulated, you can create a custom JavaScript class.
class Stack {
constructor() {
this.items = [];
}
push(value) {
this.items.push(value);
}
pop() {
return this.items.pop();
}
peek() {
return this.items.at(-1);
}
isEmpty() {
return this.items.length === 0;
}
size() {
return this.items.length;
}
}
const stack = new Stack();
stack.push(10);
stack.push(20);
stack.push(30);
console.log(stack.peek());
console.log(stack.pop());
console.log(stack.size());
console.log(stack.isEmpty());
Encapsulating the operations in a class makes the intended data structure behavior explicit and prevents application code from depending directly on the underlying array.
Stack Implementation Without a Class
A stack can also be represented with simple functions when a class is unnecessary.
const stack = [];
function push(value) {
stack.push(value);
}
function pop() {
return stack.pop();
}
function peek() {
return stack.at(-1);
}
push("HTML");
push("CSS");
push("JavaScript");
console.log(peek());
console.log(pop());
Processing a Stack Until It Is Empty
A common stack pattern is to repeatedly remove the top element until no elements remain. This processes values in reverse order of insertion.
const stack = [1, 2, 3, 4, 5];
while (stack.length > 0) {
console.log(stack.pop());
}
The output starts with 5 and ends with 1 because the most recently inserted value is removed first.
Using a Stack for Undo Operations
A stack can store previous application states or actions so that the most recent action can be undone first.
const history = [];
history.push("Typed Hello");
history.push("Added an image");
history.push("Changed the font");
const lastAction = history.pop();
console.log("Undo:", lastAction);
In a real editor, the stack could contain structured objects describing the action and the information required to reverse it.
Balanced Parentheses Using a Stack
One of the classic applications of a stack is checking whether opening and closing parentheses are correctly matched.
function isBalanced(expression) {
const stack = [];
for (const char of expression) {
if (char === "(") {
stack.push(char);
} else if (char === ")") {
if (stack.length === 0) {
return false;
}
stack.pop();
}
}
return stack.length === 0;
}
console.log(isBalanced("(a + b)"));
console.log(isBalanced("(a + b"));
console.log(isBalanced("a + b)"));
The stack stores unmatched opening parentheses. When a closing parenthesis appears, the most recent opening parenthesis is removed.
Stack for Reversing Data
Because a stack returns elements in reverse insertion order, it can be used to reverse a sequence.
function reverseString(text) {
const stack = [];
for (const char of text) {
stack.push(char);
}
let result = "";
while (stack.length > 0) {
result += stack.pop();
}
return result;
}
console.log(reverseString("JavaScript"));
Stack and Function Calls
JavaScript itself uses a call stack to keep track of function execution. When a function is called, its execution context is placed on the call stack. When the function finishes, it is removed.
function first() {
second();
}
function second() {
third();
}
function third() {
console.log("Executing third");
}
first();
Conceptually, third() is completed first, followed by
second(), and then first().
Understanding the call stack is useful when learning recursion and debugging nested function calls.
Stack for Depth-First Search
A stack can implement Depth-First Search (DFS) iteratively. Instead of relying on recursion, nodes can be explicitly stored in a stack.
const graph = {
A: ["B", "C"],
B: ["D"],
C: ["E"],
D: [],
E: []
};
const stack = ["A"];
const visited = new Set();
while (stack.length > 0) {
const node = stack.pop();
if (visited.has(node)) {
continue;
}
visited.add(node);
console.log(node);
for (const neighbor of graph[node]) {
stack.push(neighbor);
}
}
Stack Time Complexity
When the end of a JavaScript array is used as the top of the stack, the core operations are efficient.
| Operation | Typical Complexity |
|---|---|
| Push | O(1) amortized |
| Pop | O(1) |
| Peek | O(1) |
| isEmpty | O(1) |
| Size | O(1) |
| Search | O(n) |
The amortized O(1) cost for push() accounts for the
occasional internal resizing that may be required as an array grows.
Why Use the End of an Array as the Stack Top?
A JavaScript array can add and remove elements from both ends, but the end is generally
preferable for a stack because push() and
pop() are designed for efficient end operations.
Using shift() and unshift()
would make the beginning of the array the stack top, but these operations may require
existing elements to be shifted.
Stack vs Queue in JavaScript
Both stacks and queues are linear data structures, but they process elements in different orders.
| Feature | Stack | Queue |
|---|---|---|
| Ordering | LIFO | FIFO |
| Add operation | Push | Enqueue |
| Remove operation | Pop | Dequeue |
| Common uses | Undo, recursion, DFS | Scheduling, BFS, processing order |
Common Stack Mistakes in JavaScript
- Using
shift()andunshift()when efficient end operations are sufficient. - Calling
pop()without considering that an empty stack returnsundefined. - Confusing LIFO stack behavior with FIFO queue behavior.
- Removing an element when only its value needs to be inspected.
- Forgetting to check whether a stack is empty in algorithms that repeatedly pop values.
- Using a stack without tracking visited nodes in graph traversal when repeated visits are possible.
- Building large recursive solutions without considering JavaScript call stack limits.
Where Are Stacks Used?
- Browser back and forward history.
- Undo and redo functionality.
- Function calls and recursion.
- Expression parsing and evaluation.
- Balanced parentheses and delimiter checking.
- Depth-first search and graph traversal.
- Backtracking algorithms.
- Navigation through nested application states.
- Syntax processing and compiler-related algorithms.
Practical Example: Browser History
Browser-style back navigation can be modeled using a stack. Each newly visited page is pushed onto the history, and going back removes the current page from the top.
const history = [];
function visit(page) {
history.push(page);
}
function goBack() {
if (history.length <= 1) {
return "No previous page";
}
history.pop();
return history.at(-1);
}
visit("home");
visit("products");
visit("products/123");
visit("checkout");
console.log(goBack());
console.log(goBack());
Real browser history is more sophisticated than this example, but the stack concept demonstrates why recently visited states can naturally be processed in reverse order.
Key Takeaways
- A stack follows the LIFO principle: Last In, First Out.
- The main operations are push, pop, peek, isEmpty, and size.
- JavaScript arrays provide a simple and practical stack implementation.
- Use
push()andpop()at the array's end for efficient stack operations. - Peeking can be performed with
array.at(-1)without removing the top element. - Stacks are useful for undo operations, recursion, parsing, backtracking, and DFS.
- Most core array-based stack operations are O(1), with push being O(1) amortized.
- Understanding stacks is important for data structures, algorithms, and JavaScript execution concepts.