EasyStack

JavaScript Stack

Build a stack from scratch in JavaScript, understand how the language uses stacks internally, and learn patterns you can apply immediately.

Stack Using an Array

The simplest way to implement a stack in JavaScript is to wrap an array in a class. The Array methods push() and pop() already follow LIFO behavior, so the wrapper provides a cleaner, more semantic API.

class Stack {
  constructor() {
    this.items = [];
  }

  push(element) {
    this.items.push(element);
  }

  pop() {
    if (this.isEmpty()) {
      return undefined;
    }
    return this.items.pop();
  }

  peek() {
    if (this.isEmpty()) {
      return undefined;
    }
    return this.items[this.items.length - 1];
  }

  isEmpty() {
    return this.items.length === 0;
  }

  size() {
    return this.items.length;
  }

  clear() {
    this.items = [];
  }

  print() {
    console.log(this.items.toString());
  }
}

Usage:

const stack = new Stack();
stack.push(10);
stack.push(20);
stack.push(30);

console.log(stack.peek());  // 30
console.log(stack.pop());   // 30
console.log(stack.size());  // 2
console.log(stack.isEmpty()); // false

The array stores elements contiguously in memory. The push and pop methods operate on the end of the array, which is O(1) amortized. Learn about stack time complexity

Stack Using a Linked List

A linked list implementation avoids the occasional O(n) cost of array resizing. Each element is a node with a value and a pointer to the next node. The top of the stack is the head of the list.

class Node {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

class LinkedListStack {
  constructor() {
    this.top = null;
    this.count = 0;
  }

  push(value) {
    const node = new Node(value);
    node.next = this.top;
    this.top = node;
    this.count++;
  }

  pop() {
    if (this.isEmpty()) {
      return undefined;
    }
    const value = this.top.value;
    this.top = this.top.next;
    this.count--;
    return value;
  }

  peek() {
    if (this.isEmpty()) {
      return undefined;
    }
    return this.top.value;
  }

  isEmpty() {
    return this.count === 0;
  }

  size() {
    return this.count;
  }
}

Every push and pop is O(1) with no amortization. The tradeoff is extra memory for each node's pointer and the lack of cache locality compared to arrays. Full linked list stack guide

The JavaScript Call Stack

JavaScript engines like V8 (Chrome, Node.js) use a call stack to track which function is currently executing. When you call a function, a stack frame containing local variables and the return address is pushed onto the call stack. When the function returns, the frame is popped.

This is the engine's internal stack and is separate from any Stack class you build. However, it follows the same LIFO principle and is limited in size. If you write a recursive function that never returns, you will get a "Maximum call stack size exceeded" error, which is a stack overflow.

function count(n) {
  if (n === 0) return;
  count(n - 1);
}

count(5);
// Call stack: count(5) -> count(4) -> count(3) -> count(2) -> count(1) -> count(0)
// Then each frame is popped as the functions return.

Deep dive into the call stack

Built-in Array Methods That Act Like a Stack

You may not always need a custom Stack class. JavaScript arrays already behave as stacks when you only use push() and pop():

const history = [];

history.push("/home");       // push
history.push("/about");      // push
history.push("/contact");    // push

history.pop();               // "/contact"
history.pop();               // "/about"
history[history.length - 1]; // "/home" (peek)

This pattern is used throughout browser APIs and libraries. The key rule is: never use shift(), unshift(), or index access if you want true stack behavior. Learn all stack operations

Common Patterns

Undo System

Maintain an array of actions. Each action is pushed when performed. When the user presses undo, pop the last action and reverse it:

const undoStack = [];

function performAction(action) {
  action.execute();
  undoStack.push(action);
}

function undo() {
  if (undoStack.length === 0) return;
  const action = undoStack.pop();
  action.reverse();
}

Navigation History

Track visited pages in a stack. Push on navigation, pop on back button:

const navStack = [];

function navigateTo(url) {
  navStack.push(window.location.href);
  window.location.href = url;
}

function goBack() {
  if (navStack.length > 0) {
    window.location.href = navStack.pop();
  }
}

More real-world stack analogies

Performance Tips

  • Use Array.push() and Array.pop() instead of manual index manipulation.
  • If you know the maximum size in advance, pre-allocate the array: new Array(maxSize) and manage a top index.
  • Avoid spreading or copying the stack array in hot loops. Use a size counter and only access elements up to that index.
  • For millions of elements, consider a linked list to avoid array resizing pauses.
  • Profile before optimizing. The V8 engine heavily optimizes array push/pop operations.

Complete Code Example

Here is a production-ready Stack class with all common methods. Click the copy button to use it in your project:

class Stack {
  #items;
  #maxSize;

  constructor(maxSize = Infinity) {
    this.#items = [];
    this.#maxSize = maxSize;
  }

  push(element) {
    if (this.isFull()) {
      throw new Error("Stack overflow");
    }
    this.#items.push(element);
  }

  pop() {
    if (this.isEmpty()) {
      throw new Error("Stack underflow");
    }
    return this.#items.pop();
  }

  peek() {
    if (this.isEmpty()) {
      return undefined;
    }
    return this.#items[this.#items.length - 1];
  }

  isEmpty() {
    return this.#items.length === 0;
  }

  isFull() {
    return this.#items.length >= this.#maxSize;
  }

  size() {
    return this.#items.length;
  }

  clear() {
    this.#items = [];
  }

  toArray() {
    return [...this.#items].reverse();
  }

  [Symbol.iterator]() {
    let index = this.#items.length - 1;
    return {
      next: () => {
        if (index >= 0) {
          return { value: this.#items[index--], done: false };
        }
        return { done: true };
      }
    };
  }
}

const stack = new Stack(5);
stack.push("first");
stack.push("second");
stack.push("third");

for (const item of stack) {
  console.log(item); // third, second, first
}

Array implementation guide | This page

Frequently Asked Questions

Should I use an array or a linked list for a JavaScript stack?

For most use cases, an array-based stack is simpler and faster in JavaScript due to engine optimizations. Linked lists avoid resizing overhead but add memory overhead for node objects. Use arrays unless you need a fixed-size stack with guaranteed O(1) worst-case operations.

Does JavaScript have a built-in Stack class?

No. JavaScript does not have a native Stack data structure. However, the built-in Array methods push() and pop() follow LIFO behavior, so arrays are commonly used as stacks. For a cleaner API, you can wrap these methods in a Stack class.

What is the V8 call stack in JavaScript?

The V8 engine (used in Chrome and Node.js) uses a call stack to track function execution. Each function call pushes a frame onto the stack, and each return pops a frame. This is separate from any stack you implement in your code but follows the same LIFO principle.