Queue Implementation in JavaScript

Learn Queue Implementation in JavaScript with practical examples, explanations, best practices, and real-world coding techniques.

A queue in JavaScript is a linear data structure that follows the FIFO (First In, First Out) principle. The element that enters the queue first is the element that is processed first.

Queues are useful when items need to be processed in the same order in which they arrive. They are commonly used for task scheduling, print jobs, request processing, message systems, breadth-first search, buffering, and event-driven applications.

A simple real-world example is a line of people waiting at a counter.

The person who arrives first is served first. This is the basic idea behind FIFO: First In, First Out.

How a Queue Works

A queue normally has two important ends:

  • Front: The end from which elements are removed.
  • Rear: The end where new elements are added.

The main queue operations are:

  • Enqueue: Add an element to the rear.
  • Dequeue: Remove an element from the front.
  • Peek: View the front element without removing it.
  • isEmpty: Check whether the queue contains elements.
  • Size: Determine the number of elements.

Queue Using a JavaScript Array

The simplest way to represent a queue in JavaScript is with an array. You can add elements with push() and remove the first element with shift().

const queue = [];

queue.push("A");
queue.push("B");
queue.push("C");

console.log(queue);

const removed = queue.shift();

console.log("Removed:", removed);
console.log("Queue:", queue);

After adding A, B, and C, A is at the front. Calling shift() therefore removes A first.

Enqueue Operation

Enqueue means adding a new element to the rear of the queue. With an array, push() performs this operation.

const queue = [];

queue.push(10);
queue.push(20);
queue.push(30);

console.log(queue);

The queue now contains [10, 20, 30]. The front is 10 and the rear is 30.

Dequeue Operation

Dequeue removes the element from the front of the queue. With a basic array implementation, shift() can be used.

const queue = [10, 20, 30];

const item = queue.shift();

console.log("Removed:", item);
console.log("Remaining:", queue);

If the queue is empty, shift() returns undefined.

Peek at the Front Element

Peeking means looking at the front element without removing it. For an array-based queue, the front is normally at index 0.

const queue = [10, 20, 30];

const front = queue[0];

console.log("Front:", front);
console.log("Queue:", queue);

The queue remains unchanged because the front element was only inspected.

Checking Whether a Queue Is Empty

The length property can be used to determine whether the queue contains any elements.

const queue = [];

if (queue.length === 0) {
  console.log("Queue is empty");
}

Getting the Queue Size

The number of elements currently stored in an array-based queue is available through the length property.

const queue = ["Task 1", "Task 2", "Task 3"];

console.log("Queue size:", queue.length);

Creating a Queue Class in JavaScript

A custom class can encapsulate queue behavior and expose only the operations that callers need.

class Queue {
  constructor() {
    this.items = [];
  }

  enqueue(value) {
    this.items.push(value);
  }

  dequeue() {
    return this.items.shift();
  }

  peek() {
    return this.items[0];
  }

  isEmpty() {
    return this.items.length === 0;
  }

  size() {
    return this.items.length;
  }
}

const queue = new Queue();

queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);

console.log("Front:", queue.peek());
console.log("Removed:", queue.dequeue());
console.log("Size:", queue.size());
console.log("Empty:", queue.isEmpty());

The Performance Problem with shift()

Although push() is typically O(1) amortized, shift() can be O(n) because removing the first element may require the remaining elements to be shifted.

This means repeatedly calling shift() on a very large queue can become inefficient. For performance-sensitive applications, a queue can instead track a front index.

Efficient Queue with a Front Index

Instead of physically removing the first array element, maintain an index that points to the current front of the queue.

class Queue {
  constructor() {
    this.items = [];
    this.frontIndex = 0;
  }

  enqueue(value) {
    this.items.push(value);
  }

  dequeue() {
    if (this.isEmpty()) {
      return undefined;
    }

    const value = this.items[this.frontIndex];
    this.frontIndex++;

    return value;
  }

  peek() {
    return this.isEmpty()
      ? undefined
      : this.items[this.frontIndex];
  }

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

  size() {
    return this.items.length - this.frontIndex;
  }
}

const queue = new Queue();

queue.enqueue("A");
queue.enqueue("B");
queue.enqueue("C");

console.log(queue.dequeue());
console.log(queue.peek());
console.log(queue.size());

This approach makes dequeue effectively O(1), although the underlying array retains already-processed elements until it is compacted or replaced. A production implementation may periodically remove consumed entries to prevent unnecessary memory retention.

Queue with an Object

Another approach is to maintain explicit front and rear positions using an object. This avoids shifting elements when items are removed.

class Queue {
  constructor() {
    this.items = {};
    this.front = 0;
    this.rear = 0;
  }

  enqueue(value) {
    this.items[this.rear] = value;
    this.rear++;
  }

  dequeue() {
    if (this.isEmpty()) {
      return undefined;
    }

    const value = this.items[this.front];
    delete this.items[this.front];
    this.front++;

    return value;
  }

  peek() {
    if (this.isEmpty()) {
      return undefined;
    }

    return this.items[this.front];
  }

  isEmpty() {
    return this.front === this.rear;
  }

  size() {
    return this.rear - this.front;
  }
}

const queue = new Queue();

queue.enqueue(100);
queue.enqueue(200);
queue.enqueue(300);

console.log(queue.dequeue());
console.log(queue.peek());
console.log(queue.size());

Processing All Queue Elements

A common queue pattern is to process items from the front until the queue becomes empty.

const queue = ["Task A", "Task B", "Task C"];

while (queue.length > 0) {
  const task = queue.shift();

  console.log("Processing:", task);
}

The tasks are processed in the same order in which they entered the queue.

Queue for Task Scheduling

Queues are useful when tasks should be processed in arrival order. For example, a simple job processor can add pending jobs to a queue and process them one by one.

const jobs = [];

jobs.push("Generate Report");
jobs.push("Send Email");
jobs.push("Create Backup");

while (jobs.length > 0) {
  const job = jobs.shift();

  console.log("Processing:", job);
}

Queue in Breadth-First Search

One of the most important applications of a queue is Breadth-First Search (BFS). BFS processes nodes level by level, so newly discovered nodes are placed at the rear while the next node to process is taken from the front.

const graph = {
  A: ["B", "C"],
  B: ["D"],
  C: ["E"],
  D: [],
  E: []
};

const queue = ["A"];
const visited = new Set();

while (queue.length > 0) {
  const node = queue.shift();

  if (visited.has(node)) {
    continue;
  }

  visited.add(node);
  console.log(node);

  for (const neighbor of graph[node]) {
    if (!visited.has(neighbor)) {
      queue.push(neighbor);
    }
  }
}

For large graphs, replacing repeated shift() calls with a front index is a useful optimization.

Queue for Message Processing

A queue is also a natural model for messages or events waiting to be processed. Producers add messages to the rear, while a consumer processes messages from the front.

const messages = [];

messages.push("User signed in");
messages.push("Order created");
messages.push("Payment received");

while (messages.length > 0) {
  const message = messages.shift();

  console.log("Handling:", message);
}

Priority Queue vs Regular Queue

A regular queue processes elements according to arrival order. A priority queue processes elements according to priority, so an item that arrived later may be processed before an earlier item.

JavaScript does not provide a built-in general-purpose priority queue class. Priority queues are commonly implemented using a heap or another appropriate data structure.

Circular Queue Concept

A circular queue treats the storage as if the end connects back to the beginning. When the rear reaches the end of the available storage, it can wrap around to the first available position.

Circular queues are particularly useful when working with fixed-size buffers, streaming data, network packets, or systems where storage should be reused efficiently.

Queue Time Complexity

The complexity depends on the implementation. A naive array queue using shift() has a different dequeue cost from a queue that maintains a front index.

Operation Array + shift() Front Index
Enqueue O(1) amortized O(1) amortized
Dequeue O(n) typical O(1)
Peek O(1) O(1)
isEmpty O(1) O(1)
Size O(1) O(1)

Queue vs Stack in JavaScript

The main difference between a queue and a stack is the order in which elements are removed.

Feature Queue Stack
Ordering FIFO LIFO
Add Rear Top
Remove Front Top
Typical applications Scheduling, BFS, buffering Undo, recursion, DFS

Common Queue Mistakes in JavaScript

  • Confusing FIFO queue behavior with LIFO stack behavior.
  • Using pop() when the requirement is to remove the oldest element.
  • Repeatedly using shift() for very large queues without considering its cost.
  • Reading the rear element when the algorithm requires the front element.
  • Calling dequeue without handling an empty queue.
  • Forgetting to track visited nodes when using a queue for graph traversal.
  • Keeping processed elements indefinitely when using a front-index implementation.

Where Are Queues Used?

  • Task and job scheduling.
  • Print queues.
  • Message processing systems.
  • Request processing and buffering.
  • Breadth-First Search.
  • Network and streaming buffers.
  • Producer-consumer systems.
  • Event processing pipelines.
  • Customer-service and ticketing systems.

Practical Example: Processing Customer Requests

Suppose a server receives customer requests that should be handled in the same order in which they arrive. A queue can represent the pending requests.

const requests = [];

requests.push({
  id: 101,
  action: "Create Account"
});

requests.push({
  id: 102,
  action: "Update Profile"
});

requests.push({
  id: 103,
  action: "Download Report"
});

while (requests.length > 0) {
  const request = requests.shift();

  console.log(
    "Processing request:",
    request.id,
    request.action
  );
}

The request with ID 101 is processed before 102, and 102 is processed before 103. This preserves the arrival order represented by the queue.

Key Takeaways

  • A queue follows the FIFO principle: First In, First Out.
  • New elements are added at the rear and removed from the front.
  • The core operations are enqueue, dequeue, peek, isEmpty, and size.
  • JavaScript arrays can provide a simple queue implementation.
  • push() and shift() are easy to understand but repeated shift operations can be expensive.
  • A front-index implementation can provide O(1) dequeue behavior.
  • Queues are fundamental to BFS, scheduling, buffering, and message processing.
  • Priority queues and circular queues solve different problems from a basic FIFO queue.

Code Examples

const queue = [];

queue.push("A");
queue.push("B");
queue.push("C");

console.log("Front:", queue[0]);

console.log("Removed:", queue.shift());
console.log("Removed:", queue.shift());

console.log("Remaining:", queue);
    
class Queue {
  constructor() {
    this.items = [];
  }

  enqueue(value) {
    this.items.push(value);
  }

  dequeue() {
    return this.items.shift();
  }

  peek() {
    return this.items[0];
  }

  isEmpty() {
    return this.items.length === 0;
  }

  size() {
    return this.items.length;
  }
}

const queue = new Queue();

queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);

console.log(queue.peek());
console.log(queue.dequeue());
console.log(queue.size());
    
class Queue {
  constructor() {
    this.items = [];
    this.frontIndex = 0;
  }

  enqueue(value) {
    this.items.push(value);
  }

  dequeue() {
    if (this.isEmpty()) {
      return undefined;
    }

    const value = this.items[this.frontIndex];
    this.frontIndex++;

    return value;
  }

  peek() {
    return this.isEmpty()
      ? undefined
      : this.items[this.frontIndex];
  }

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

  size() {
    return this.items.length - this.frontIndex;
  }
}

const queue = new Queue();

queue.enqueue("A");
queue.enqueue("B");
queue.enqueue("C");

console.log(queue.dequeue());
console.log(queue.peek());
console.log(queue.size());
    
const tasks = [];

tasks.push("Build Project");
tasks.push("Run Tests");
tasks.push("Deploy Application");

while (tasks.length > 0) {
  const task = tasks.shift();

  console.log("Processing:", task);
}
    
const graph = {
  A: ["B", "C"],
  B: ["D"],
  C: ["E"],
  D: [],
  E: []
};

const queue = ["A"];
const visited = new Set();

while (queue.length > 0) {
  const node = queue.shift();

  if (visited.has(node)) {
    continue;
  }

  visited.add(node);
  console.log(node);

  for (const neighbor of graph[node]) {
    if (!visited.has(neighbor)) {
      queue.push(neighbor);
    }
  }
}
    

Pro Tip

For small queues, push() and shift() are simple and readable. For large or performance-sensitive queues, avoid repeated shift() calls and use a front index or another queue implementation that supports O(1) dequeue operations.

🎨 IgKnight Docs

Master HTML, CSS, JS, Python, .NET and more with easy-to-follow lessons and practical code examples.

© 2025 IgKnight Learning Platform. All rights reserved.