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
dequeuewithout 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()andshift()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.