Exploring Stacks and Queues via JavaScript
A practical guide to stacks and queues in JavaScript, covering array and linked list implementations, LIFO/FIFO behavior, code examples, and Big O trade-offs.
Drake Nguyen
Founder · System Architect
Introduction
This guide explains stacks and queues in JavaScript: two simple but powerful abstract data types used across algorithms and applications. You’ll learn what LIFO and FIFO mean, when to choose an array vs a linked list implementation, and see clear examples for stack and queue implementation in JavaScript.
Prerequisites
Familiarity with basic JavaScript, arrays, and classes is helpful. A basic understanding of linked lists and Big O notation will make the examples easier to follow, but the code and explanations are kept approachable for most developers learning JavaScript data structures.
What are Stacks?
A stack is an abstract data type that follows LIFO (last in, first out). Think of a stack of plates: you add to the top and remove from the top. Stacks are used in many places in JavaScript, including the call stack for function execution and recursion.
Array-based stack (simple and fast)
Using an array is the shortest route to a stack in JavaScript. If you only use push and pop, both operations are amortized O(1).
// stack using array
const stack = [];
function push(val) {
stack.push(val); // add to top
}
function pop() {
return stack.pop(); // remove from top
}
push('one');
push('two');
push('three');
console.log(pop()); // 'three'
console.log(stack); // ['one', 'two']
Linked list stack (better memory behavior for large data)
A linked list implementation avoids resizing costs and can be a better choice when working with many nodes or streaming data. This example shows a minimal linked list stack implementation for a stack implementation JavaScript developers can extend.
// linked list stack
class Node {
constructor(val) {
this.val = val;
this.next = null;
}
}
class Stack {
constructor() {
this.head = null; // top of stack
this.length = 0;
}
push(val) {
const node = new Node(val);
node.next = this.head;
this.head = node;
this.length++;
}
pop() {
if (!this.head) return null;
const val = this.head.val;
this.head = this.head.next;
this.length--;
return val;
}
}
const s = new Stack();
s.push('one');
s.push('two');
console.log(s.pop()); // 'two'
What are Queues?
A queue follows FIFO (first in, first out). Items are added at one end (tail) and removed from the other (head), like people lining up for service. Queues are useful for event handling, breadth-first search, and producer-consumer patterns in JavaScript.
Array-based queue (easy, but watch out for shift)
Using an array with push and shift gives clear semantics, but shift is O(n) because it reindexes elements. That can be costly for queues with many items.
// queue using array
const queue = [];
function enqueue(val) {
queue.push(val); // add to tail
}
function dequeue() {
return queue.shift(); // remove from head (O(n))
}
enqueue('one');
enqueue('two');
enqueue('three');
console.log(dequeue()); // 'one'
console.log(queue); // ['two', 'three']
Linked list queue (efficient enqueue/dequeue)
A linked list queue with head and tail pointers supports enqueue and dequeue in O(1) time without shifting. This linked list queue JavaScript implementation is ideal for large or streaming queues.
// linked list queue
class NodeQ {
constructor(val) {
this.val = val;
this.next = null;
}
}
class Queue {
constructor() {
this.head = null; // remove from head
this.tail = null; // add to tail
this.length = 0;
}
enqueue(val) {
const node = new NodeQ(val);
if (!this.head) {
this.head = node;
this.tail = node;
} else {
this.tail.next = node;
this.tail = node;
}
this.length++;
}
dequeue() {
if (!this.head) return null;
const val = this.head.val;
this.head = this.head.next;
if (!this.head) this.tail = null;
this.length--;
return val;
}
}
const q = new Queue();
q.enqueue('one');
q.enqueue('two');
console.log(q.dequeue()); // 'one'
Complexity and when to choose which
- Stack (array push/pop): O(1) average for push and pop; simple and memory-friendly for many tasks.
- Stack (linked list): O(1) push/pop, good for very large or streamed datasets where contiguous memory is undesirable.
- Queue (array push/shift): enqueue O(1), dequeue O(n) due to shift; acceptable for small queues or infrequent removals.
- Queue (linked list): enqueue and dequeue O(1); preferred for high-volume queues or real-time systems.
Common use cases
- Use stacks in JavaScript for recursion utilities, undo features, and depth-first traversals.
- Use queues in JavaScript for breadth-first search, task scheduling, and streaming processors.
- Implement stacks and queues using linked lists when you need predictable O(1) operations without reallocation or reindexing.
Further notes
When designing with stacks and queues in JavaScript, consider memory patterns, expected throughput, and whether you need persistence or concurrency control. For example, avoid array shift for high-frequency dequeue operations — a linked list queue or a ring buffer yields better performance.
Closing thoughts
Understanding stacks and queues in JavaScript will make it easier to reason about algorithm behavior and choose the right data structure for your workload. Whether you implement a stack using an array or a linked list stack, or a queue using a linked list queue, the principles of LIFO and FIFO remain the same and are foundational to effective JavaScript data structures.