A queue is a linear data structure that follows the First-In-First-Out (FIFO) principle. Think of it like a line at a coffee shop: the first person to join the line is the first person to be served. This fair ordering makes queues perfect for managing tasks, requests, and data that needs to be processed in the order it arrives.
Queues are everywhere in computing—from managing print jobs and handling network requests to implementing breadth-first search algorithms. By the end of this tutorial, you’ll understand how queues work, when to use them, and how to implement them efficiently.
If you’re new to time and space complexity, start with our guide to Big-O Notation. You may also want to review Arrays, Linked Lists, and Stacks to understand related data structures.
Queues provide O(1) time complexity for enqueue (add) and dequeue (remove) operations, making them extremely efficient for FIFO processing.
What is a queue?
A queue is a collection of elements with two principal operations:
Enqueue: Add an element to the back (rear) of the queue
Dequeue: Remove and return the element from the front of the queue
Additionally, most queue implementations include:
3. Peek (or Front): View the front element without removing it
4. IsEmpty: Check if the queue is empty
5. Size: Get the number of elements in the queue
The key constraint: elements are added at one end (rear) and removed from the other end (front), maintaining FIFO order.
Why use a queue?
Choose a queue when:
You need FIFO (First-In-First-Out) processing
You’re managing tasks or requests that should be handled in order
Understanding the difference between queues and stacks is crucial:
Feature
Queue
Stack
Order
FIFO (First-In-First-Out)
LIFO (Last-In-First-Out)
Add operation
Enqueue (at rear)
Push (at top)
Remove operation
Dequeue (from front)
Pop (from top)
Use cases
Task scheduling, BFS
Function calls, DFS, undo/redo
Analogy
Line at a store
Stack of plates
Core operations and complexity
All primary queue operations are extremely efficient:
Enqueue: O(1) — add element to rear
Dequeue: O(1) — remove and return front element
Peek/Front: O(1) — view front element without removing
IsEmpty: O(1) — check if queue is empty
Size: O(1) — get number of elements
Space: O(n) — where n is the number of elements in the queue
Queue implementation
Queues can be implemented using arrays or linked lists. Array-based implementations are simpler but may require resizing. Linked list implementations are more flexible and never need resizing.
Performance Note: Using shift() (JavaScript) or pop(0) (Python) or remove(0) (Java) on arrays is O(n) because it requires shifting all remaining elements. For better performance with many dequeue operations, use a linked list implementation or a circular array.
Linked list-based queue
For optimal performance, implement queues using linked lists with both head and tail pointers:
Use a queue to generate binary representations efficiently:
functiongenerateBinaryNumbers(n) {
constresult= [];
constqueue= ['1'];
for (leti=0; i<n; i++) {
constbinary=queue.shift();
result.push(binary);
// Generate next binary numbers by appending 0 and 1
queue.push(binary+'0');
queue.push(binary+'1');
}
returnresult;
}
console.log(generateBinaryNumbers(5)); // ['1', '10', '11', '100', '101']
from collections import deque
defgenerate_binary_numbers(n):
result = []
queue = deque(['1'])
for i in range(n):
binary = queue.popleft()
result.append(binary)
# Generate next binary numbers by appending 0 and 1 queue.append(binary +'0')
queue.append(binary +'1')
return result
print(generate_binary_numbers(5)) # ['1', '10', '11', '100', '101']
import java.util.*;publicclassBinaryNumbers{publicstatic List<String>generateBinaryNumbers(int n){ List<String> result =new ArrayList<>(); Queue<String> queue =new LinkedList<>(); queue.offer("1");for(int i = 0; i < n; i++){ String binary = queue.poll(); result.add(binary);// Generate next binary numbers by appending 0 and 1
queue.offer(binary +"0"); queue.offer(binary +"1");}return result;}publicstaticvoidmain(String[] args){ System.out.println(generateBinaryNumbers(5));// [1, 10, 11, 100, 101]
}}
3. Implement a stack using two queues
Challenge: Implement stack operations using only queue operations:
classStackUsingQueues {
constructor() {
this.q1= [];
this.q2= [];
}
push(x) {
// Add to q2
this.q2.push(x);
// Move all elements from q1 to q2
while (this.q1.length>0) {
this.q2.push(this.q1.shift());
}
// Swap q1 and q2
[this.q1, this.q2] = [this.q2, this.q1];
}
pop() {
if (this.q1.length===0) {
returnundefined;
}
returnthis.q1.shift();
}
top() {
if (this.q1.length===0) {
returnundefined;
}
returnthis.q1[0];
}
empty() {
returnthis.q1.length===0;
}
}
// Example usage
conststack=newStackUsingQueues();
stack.push(1);
stack.push(2);
stack.push(3);
console.log(stack.top()); // 3
console.log(stack.pop()); // 3
console.log(stack.pop()); // 2
from collections import deque
classStackUsingQueues:
def __init__(self):
self.q1 = deque()
self.q2 = deque()
defpush(self, x):
# Add to q2 self.q2.append(x)
# Move all elements from q1 to q2while self.q1:
self.q2.append(self.q1.popleft())
# Swap q1 and q2 self.q1, self.q2 = self.q2, self.q1
defpop(self):
ifnot self.q1:
returnNonereturn self.q1.popleft()
deftop(self):
ifnot self.q1:
returnNonereturn self.q1[0]
defempty(self):
return len(self.q1) ==0# Example usagestack = StackUsingQueues()
stack.push(1)
stack.push(2)
stack.push(3)
print(stack.top()) # 3print(stack.pop()) # 3print(stack.pop()) # 2