Reading Time 5
Number of Words 1022
Queues and stacks are fundamental abstract data structures. They power many algorithms and help manage collections of data in controlled ways. In JavaScript, you can implement them using arrays (or custom linked structures). Below is a clear guide to both, how they differ, when to use them, and sample code.
Table of Contents
- What Are Stacks & Queues?
- Stack: LIFO (Last In, First Out)
- Queue: FIFO (First In, First Out)
- Implementations in JavaScript
- Using Arrays
- Using Linked Lists
- Use Cases & Examples
- Performance Considerations
- Tips & Best Practices
1. What Are Stacks & Queues?
- Stack: A collection where the last element added (pushed) is the first one removed (popped). Think of a stack of plates.
- Queue: A collection where the first element added is also the first one removed. Think of a line (queue) of people.
The difference is in the order of insertion and removal:
- Stack → last in, first out
- Queue → first in, first out
2. Stack: LIFO (Last In, First Out)
Core operations
push(item): add an item to the toppop(): remove and return the top itempeek()(ortop): view the top item without removingisEmpty(): check whether the stack is emptysize(): number of items in the stack
Example: Stack using array
class Stack {
constructor() {
this.items = [];
}
push(element) {
this.items.push(element);
}
pop() {
if (this.isEmpty()) {
return undefined;
}
return this.items.pop();
}
peek() {
return this.items[this.items.length - 1];
}
isEmpty() {
return this.items.length === 0;
}
size() {
return this.items.length;
}
}
// Demo
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.pop()); // 20
console.log(stack.isEmpty()); // false
console.log(stack.pop()); // 10
console.log(stack.isEmpty()); // true
3. Queue: FIFO (First In, First Out)
Core operations
enqueue(item): add an item at the backdequeue(): remove and return the front itemfront()orpeek(): view the front item without removalisEmpty()size()
Example: Queue using array
class Queue {
constructor() {
this.items = [];
}
enqueue(element) {
this.items.push(element);
}
dequeue() {
if (this.isEmpty()) {
return undefined;
}
return this.items.shift();
}
front() {
return this.items[0];
}
isEmpty() {
return this.items.length === 0;
}
size() {
return this.items.length;
}
}
// Demo
const queue = new Queue();
queue.enqueue('Alice');
queue.enqueue('Bob');
queue.enqueue('Charlie');
console.log(queue.front()); // "Alice"
console.log(queue.dequeue()); // "Alice"
console.log(queue.front()); // "Bob"
console.log(queue.isEmpty()); // false
console.log(queue.dequeue()); // "Bob"
console.log(queue.dequeue()); // "Charlie"
console.log(queue.isEmpty()); // true
4. Alternative Implementation: Linked List
Using an array is simple, but operations like shift() (removing from front) are O(n) because elements must be reindexed. If you expect many dequeue operations, a linked list–based queue is more efficient (O(1) enqueue & dequeue).
Queue via singly linked list
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}
class LinkedQueue {
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
enqueue(value) {
const newNode = new Node(value);
if (!this.tail) {
this.head = newNode;
this.tail = newNode;
} else {
this.tail.next = newNode;
this.tail = newNode;
}
this.length++;
}
dequeue() {
if (!this.head) {
return undefined;
}
const removedValue = this.head.value;
this.head = this.head.next;
if (!this.head) {
this.tail = null;
}
this.length--;
return removedValue;
}
front() {
return this.head ? this.head.value : undefined;
}
isEmpty() {
return this.length === 0;
}
size() {
return this.length;
}
}
// Demo
const lq = new LinkedQueue();
lq.enqueue(1);
lq.enqueue(2);
lq.enqueue(3);
console.log(lq.front()); // 1
console.log(lq.dequeue()); // 1
console.log(lq.dequeue()); // 2
console.log(lq.dequeue()); // 3
console.log(lq.isEmpty()); // true
You can likewise build a linked stack if needed, using push/pop on the head of the list.
5. Use Cases & Examples
Understanding when to use stacks vs queues is key.
Use-cases for Stacks
- Backtracking / undo functionality
- Function call stack / recursion
- Syntax parsing / parentheses matching
Example: checking for balanced parentheses:
function isBalanced(str) {
const stack = new Stack();
const pairs = { ')': '(', ']': '[', '}': '{' };
for (const ch of str) {
if (['(', '[', '{'].includes(ch)) {
stack.push(ch);
} else if ([')', ']', '}'].includes(ch)) {
if (stack.isEmpty() || stack.pop() !== pairs[ch]) {
return false;
}
}
}
return stack.isEmpty();
}
console.log(isBalanced("({[]})")); // true
console.log(isBalanced("(]")); // false
Use-cases for Queues
- Job scheduling / task queues
- Breadth-first search (BFS) in graphs/trees
- Asynchronous message processing / event loops
Example: BFS traversal of a tree:
function bfs(root) {
const result = [];
const queue = new Queue();
queue.enqueue(root);
while (!queue.isEmpty()) {
const node = queue.dequeue();
result.push(node.value);
if (node.left) queue.enqueue(node.left);
if (node.right) queue.enqueue(node.right);
}
return result;
}
// Suppose a simple binary tree node structure
// Use bfs(rootNode) to get level order traversal
6. Performance Considerations
- Using
Array.shift()for dequeue is O(n) (elements must shift). For many operations, this becomes inefficient. - Using a linked list or a circular buffer helps maintain O(1) enqueue & dequeue.
- Be cautious with memory: track references to avoid leaks.
- For small datasets or light workloads, using arrays may be acceptable and simpler.
7. Tips & Best Practices
- Pick the right implementation for your expected usage pattern.
- Keep your API consistent (names like
enqueue,dequeue,push,pop,peek). - Handle edge cases: empty structures, single elements.
- Avoid exposing internal state (e.g. internal array). Use class methods instead.
- Write unit tests for corner conditions (dequeue from empty, etc.).