Skip to main content

Stacks, Queues and Linked Lists in JavaScript

Arrays can imitate all three of these structures — the question is when the imitation costs you. The short version: stacks map perfectly onto arrays, queues do not, and linked lists exist for the cases where re-indexing an array is the bottleneck.

Stacks

Last in, first out. The most recent item is always the next one out — undo history, matching brackets, the call stack itself.

A JavaScript array is a stack: push and pop both work at the end in O(1). No wrapper class needed.

function isBalanced(code) {
const stack = [];
const pairs = { ")": "(", "]": "[", "}": "{" };

for (const ch of code) {
if (ch === "(" || ch === "[" || ch === "{") {
stack.push(ch);
} else if (ch in pairs) {
if (stack.pop() !== pairs[ch]) return false;
}
}
return stack.length === 0;
}

console.log(isBalanced("function add(a, b) { return [a, b]; }")); // true
console.log(isBalanced("if (x { y }")); // false

Every opener is pushed; every closer must match the most recent unmatched opener — exactly the top of the stack. O(n) time, O(n) worst-case space.

Stacks also power iterative DFS (see Trees and Graphs Basics) and any "most recent thing first" requirement: browser back buttons, expression evaluation, undo.

Queues

First in, first out. Whoever arrived first is served first — task queues, message processing, BFS.

The array trap: shift removes the first element but re-indexes everything behind it, so an array-based queue costs O(n) per dequeue. Two clean O(1) alternatives:

Index-pointer queue — simplest, fine when the queue eventually empties:

class Queue {
#items = [];
#head = 0;

enqueue(item) {
this.#items.push(item);
}

dequeue() {
if (this.isEmpty()) return undefined;
const item = this.#items[this.#head];
this.#items[this.#head] = undefined;
this.#head++;
return item;
}

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

const helpDesk = new Queue();
helpDesk.enqueue("Ayesha");
helpDesk.enqueue("Ibrahim");
helpDesk.enqueue("Zakariya");

console.log(helpDesk.dequeue()); // "Ayesha" (first in, first out)
console.log(helpDesk.dequeue()); // "Ibrahim"

Linked-list queue — enqueue at the tail, dequeue at the head, both O(1), memory released as you go. Build it from the linked list below.

Linked Lists

A linked list is a chain of nodes, each holding a value and a pointer to the next node:

There is no built-in linked list in JavaScript — you build it from a small node class:

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

class LinkedList {
head = null;

prepend(value) {
const node = new ListNode(value);
node.next = this.head;
this.head = node;
}

removeHead() {
if (this.head === null) return undefined;
const value = this.head.value;
this.head = this.head.next;
return value;
}

toArray() {
const out = [];
let current = this.head;
while (current !== null) {
out.push(current.value);
current = current.next;
}
return out;
}
}

const list = new LinkedList();
list.prepend("Ayesha");
list.prepend("Hafsa");
list.prepend("Rizwan");

console.log(list.toArray()); // ["Rizwan", "Hafsa", "Ayesha"]
console.log(list.removeHead()); // "Rizwan"
console.log(list.toArray()); // ["Hafsa", "Ayesha"]

Array vs linked list

OperationArrayLinked list
Access by indexO(1)O(n) — walk the chain
Insert/remove at frontO(n)O(1)
Insert/remove at endO(1)O(1) with a tail pointer
Insert after a known nodeO(n)O(1) — repoint two links
Memory layoutContiguous, cache-friendlyScattered, one pointer per node

When a linked list actually beats an array

  • Constant-time removal from the front — queues, schedulers, rate limiters.
  • Splicing at a known position. If you already hold a reference to a node, inserting or deleting next to it repoints two links instead of shifting thousands of elements. This is how LRU caches get O(1) eviction: a hash map for lookup plus a doubly linked list for ordering.
  • No reallocation spikes. Arrays occasionally copy themselves when they grow; lists never do.

For everything else — iteration, random access, small collections — arrays win, and their cache-friendliness means they often win even when the Big-O says otherwise. Reach for a list when the front or middle of a large collection churns constantly.

The classic interview questions

Linked lists are interview favorites because pointer manipulation is easy to get subtly wrong. In-place reversal is the one to know cold:

function reverseList(head) {
let prev = null;
let current = head;

while (current !== null) {
const next = current.next;
current.next = prev;
prev = current;
current = next;
}
return prev;
}

O(n) time, O(1) space — each node's pointer is flipped exactly once. Cycle detection and middle-of-list, the other two staples, are covered under fast and slow pointers in Common Interview Patterns.

Practice problems: the Stack, Queue, and Linked List sections of the practice catalog.