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
| Operation | Array | Linked list |
|---|---|---|
| Access by index | O(1) | O(n) — walk the chain |
| Insert/remove at front | O(n) | O(1) |
| Insert/remove at end | O(1) | O(1) with a tail pointer |
| Insert after a known node | O(n) | O(1) — repoint two links |
| Memory layout | Contiguous, cache-friendly | Scattered, 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.