JavaScript
Data Structures
Stack Implementation
Queue Implementation
Programming Tips

How do you implement a Stack and a Queue in JavaScript?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Stacks and queues look similar because both store items in sequence, but they enforce different removal rules. A stack is last in, first out. A queue is first in, first out. That difference drives both the API and the implementation.

In JavaScript, the simplest stack uses an array. A queue can also start with an array, but if you care about predictable performance, it is better to avoid shift() and track the front of the queue explicitly.

Implement a Stack with an Array

Arrays are a natural fit for stacks because push() and pop() operate at the end.

javascript
1class Stack {
2  constructor() {
3    this.items = [];
4  }
5
6  push(value) {
7    this.items.push(value);
8  }
9
10  pop() {
11    return this.items.length === 0 ? undefined : this.items.pop();
12  }
13
14  peek() {
15    return this.items.length === 0
16      ? undefined
17      : this.items[this.items.length - 1];
18  }
19
20  isEmpty() {
21    return this.items.length === 0;
22  }
23
24  size() {
25    return this.items.length;
26  }
27}
28
29const stack = new Stack();
30stack.push(10);
31stack.push(20);
32stack.push(30);
33
34console.log(stack.peek());
35console.log(stack.pop());
36console.log(stack.size());

This is enough for most stack use cases, including undo logic, expression parsing, and depth-first traversal. The core idea is that insertion and removal happen at the same end of the structure.

Implement a Queue Without shift()

The obvious queue implementation is push() plus shift(). It works, but shift() has to move the remaining elements down, which makes it a poor choice when the queue gets large or when enqueue and dequeue happen frequently.

A better queue tracks head and tail indexes:

javascript
1class Queue {
2  constructor() {
3    this.items = {};
4    this.head = 0;
5    this.tail = 0;
6  }
7
8  enqueue(value) {
9    this.items[this.tail] = value;
10    this.tail += 1;
11  }
12
13  dequeue() {
14    if (this.isEmpty()) {
15      return undefined;
16    }
17
18    const value = this.items[this.head];
19    delete this.items[this.head];
20    this.head += 1;
21    return value;
22  }
23
24  front() {
25    return this.isEmpty() ? undefined : this.items[this.head];
26  }
27
28  isEmpty() {
29    return this.head === this.tail;
30  }
31
32  size() {
33    return this.tail - this.head;
34  }
35}
36
37const queue = new Queue();
38queue.enqueue("A");
39queue.enqueue("B");
40queue.enqueue("C");
41
42console.log(queue.front());
43console.log(queue.dequeue());
44console.log(queue.size());

This version keeps queue operations simple even when the queue grows, because removing the front element does not require reshaping the whole collection.

Choose the Simpler Form Only When It Is Enough

For a short script or interview warm-up, an array queue may still be acceptable:

javascript
1const queue = [];
2queue.push("A");
3queue.push("B");
4
5console.log(queue.shift());
6console.log(queue.shift());

The issue is not correctness. It is cost. For tiny workloads, the simple version is fine. For repeated queue traffic, the indexed version is a better baseline.

That distinction is useful in interviews and real code. It shows that you understand both the abstract data structure and the behavior of the underlying JavaScript operations.

Common Pitfalls

The biggest mistake is treating a queue exactly like a stack because both use "add" and "remove" operations. The ordering rule is the whole point of the structure, so the API should make that rule obvious with names such as enqueue and dequeue.

Another common issue is implementing a queue with shift() without thinking about the cost of repeated front removals.

It is also easy to return special strings such as "Underflow" when the structure is empty. In JavaScript, returning undefined is usually the simpler and more natural empty-result value.

Finally, if code outside the class manipulates the backing storage directly, the stack or queue abstraction stops protecting the ordering behavior that made it useful.

Summary

  • Use an array-backed class for stacks with push() and pop().
  • For queues, prefer head and tail indexes when performance matters.
  • Keep the operations explicit: push, pop, peek, enqueue, dequeue, and front.
  • Use the simplest implementation that matches the workload.
  • Preserve the abstraction so the rest of the program cannot break the ordering rules accidentally.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.