V Talks Tech · 15 JUL 2026
Data Structures #5: The Queue - First In, First Out
This post explores the Queue data structure and its First-In, First-Out principle. It explains how to achieve O(1) performance using linked lists and circular arrays across multiple programming languages.
- By
- Vibhishan Ranga
- Read
- 7 min
- Tags
In our last post, we explored the Stack, an Abstract Data Type defined by its strict Last-In, First-Out (LIFO) rule. Today, we look at its counterpart: a structure that governs fairness and order in both the physical and digital worlds, known as the Queue.
To understand a queue, simply picture a line of people waiting to withdraw cash at an ATM. The rules are universally understood. The first person to join the line is the first person to use the ATM and leave. When you arrive, you join at the very back of the line. There is no cutting in the middle, and the ATM only serves the person at the very front.
This is the FIFO: First-In, First-Out principle. It is the exact opposite of the Stack's book-pile logic, and it is essential for any system where tasks must be processed in the exact order they were received.
Under the Hood: Enforcing the Rules and Time Complexity
Like the Stack, the Queue is an Abstract Data Type (ADT). It is a set of rules enforced over an underlying data structure, typically an array or a linked list.
A standard queue exposes three primary operations:
- Enqueue: Add an element to the back (the end of the ATM line).
- Dequeue: Remove and return the element at the front (the person finishing at the ATM).
- Peek (or Front): Look at the element at the front without removing it.
The Standard Array Problem
While building a Stack on top of a dynamic array is perfectly efficient, building a Queue on a basic array exposes a massive performance flaw.
If you use a standard array and enqueue items to the end, it takes O(1) constant time. However, your dequeue operation must remove the first item at index 0. In a standard array, removing the first item forces every subsequent item to physically shift one position to the left in memory to fill the gap. If your queue has one million items, a single dequeue operation requires one million shift operations. This makes dequeue a disastrous O(n) linear operation.
Achieving True O(1) Performance
To achieve the holy grail of O(1) constant time for both Enqueue and Dequeue, engineers use two clever architectural workarounds:
- The Linked List Queue: By maintaining two separate pointers, one referencing the Head (front) and one referencing the Tail (back) of a linked list, operations become instantaneous. To enqueue, you create a new node, set the current Tail's
nextreference to this node, and update the Tail pointer. To dequeue, you read the Head node's data and update the Head pointer to equalHead.next. Because we are only updating memory addresses and not shifting data, both operations take exactlyO(1)time. - The Circular Array (Ring Buffer): This approach uses a fixed-size array alongside two integer variables acting as index pointers:
frontandrear. When you enqueue, you insert at therearindex and increment it. When you dequeue, you read thefrontindex and increment it. The magic happens when a pointer reaches the end of the array: it wraps around back to index 0 using modulo arithmetic (index % capacity). By treating the array as a continuous loop, we completely avoid theO(n)shifting problem.
Because of these strict FIFO rules and efficient internal memory management, the performance profile of a properly implemented queue looks like this:
- Access / Search:
O(n). You cannot access the middle of the queue without dequeuing everything in front of it. - Enqueue:
O(1). - Dequeue:
O(1).
Language Implementations: The Deque Dominance
In modern programming languages, the Queue is almost always implemented under the hood using a Deque (Double-Ended Queue, pronounced "deck"). A Deque is a generalized queue that allows O(1) insertions and deletions at both ends.
C++: std::queue (The Adapter)
Just like std::stack, C++ provides std::queue as a container adapter. By default, it wraps std::deque.
#include <iostream>
#include <queue>
#include <string>
int main() {
std::queue<std::string> atmQueue;
atmQueue.push("Alice"); // Enqueue: O(1)
atmQueue.push("Bob"); // Enqueue: O(1)
std::cout << "Currently serving: " << atmQueue.front() << std::endl; // Peek: Alice
atmQueue.pop(); // Dequeue Alice: O(1)
std::cout << "Next in line: " << atmQueue.front() << std::endl; // Peek: Bob
return 0;
}
Internals: The underlying std::deque in C++ is usually implemented as a segmented array. Instead of one massive block of contiguous memory, it manages an array of pointers that point to fixed-size memory chunks. When a chunk fills up at either end, a new chunk is allocated. This provides fast O(1) operations at both ends while maintaining much better CPU cache locality than a pure linked list.
Java: The Queue Interface
Java provides an explicit Queue interface. While you can implement it with a LinkedList, the modern and highly performant choice is the ArrayDeque.
import java.util.ArrayDeque;
import java.util.Queue;
public class Main {
public static void main(String[] args) {
Queue<String> atmQueue = new ArrayDeque<>();
atmQueue.offer("Alice"); // Enqueue
atmQueue.offer("Bob");
System.out.println("Serving: " + atmQueue.peek()); // Alice
System.out.println("Finished: " + atmQueue.poll()); // Dequeue Alice
}
}
Internals: Java's ArrayDeque uses the exact Circular Array concept mentioned earlier. Under the hood, it maintains a resizable array with head and tail index pointers that wrap around the array's capacity using bitwise operations. This guarantees amortized O(1) performance and avoids the memory overhead of linked list node objects.
Python: collections.deque
In Python, using a standard list as a queue is a well-known anti-pattern because my_list.pop(0) forces an O(n) shift of every element in the list. The standard library provides collections.deque specifically to solve this.
from collections import deque
atm_queue = deque()
atm_queue.append("Alice") # Enqueue: O(1)
atm_queue.append("Bob")
print(f"Serving: {atm_queue[0]}") # Peek: Alice
served = atm_queue.popleft() # Dequeue: O(1)
print(f"Finished: {served}")
Internals: CPython implements deque as a doubly linked list of blocks, where each block holds an array of 64 elements. This hybrid approach gets the best of both worlds. It provides fast O(1) appends and pops from both ends without massive memory reallocations, while the block structure keeps memory overhead relatively low compared to a traditional linked list.
JavaScript: Escaping the shift() Trap
JavaScript is the dangerous outlier. Because JS does not have a native Queue or Deque object in its standard library, developers frequently use a standard array to simulate one.
// The Anti-Pattern
const atmQueue = [];
atmQueue.push("Alice"); // Enqueue: Fast
atmQueue.push("Bob");
console.log(atmQueue[0]); // Peek: Alice
console.log(atmQueue.shift()); // Dequeue Alice... but at an O(n) cost!
Internals: Array.prototype.shift() removes the first element and recalculates the indices of all subsequent elements. Under the hood, V8 and other JavaScript engines must perform an O(n) memory copy. For small arrays, this is negligible. For high-performance applications like an asynchronous task scheduler, relying on shift() causes severe performance bottlenecks.
To achieve true O(1) queue operations in JavaScript, developers typically write a custom Queue class utilizing a standard JavaScript object to simulate pointer logic:
// The O(1) JavaScript Solution
class Queue {
constructor() {
this.items = {};
this.headIndex = 0;
this.tailIndex = 0;
}
enqueue(item) {
this.items[this.tailIndex] = item;
this.tailIndex++;
}
dequeue() {
if (this.headIndex === this.tailIndex) return null; // Queue is empty
const item = this.items[this.headIndex];
delete this.items[this.headIndex];
this.headIndex++;
return item;
}
peek() {
return this.items[this.headIndex];
}
}
In this implementation, we simply track the current front and back using integer keys on an object. Enqueuing and dequeuing are just basic object property lookups and additions, guaranteeing O(1) execution time.
Famous Algorithms & Use Cases
Queues are the backbone of systems that require scheduling, buffering, or exploring data in a radial pattern.
- Breadth-First Search (BFS): This is the Queue's claim to fame. When searching a graph or a tree, a queue ensures you explore all nodes at the current depth level before moving deeper. You enqueue the root, then dequeue it, process it, and enqueue all of its direct children, repeating until the queue is empty.
- Message Queues & Task Scheduling: In real-world infrastructure, massive distributed systems use enterprise-level queue software like RabbitMQ or Apache Kafka to pass messages between servers asynchronously. For instance, a web server enqueues an email-sending task, allowing it to immediately return to the user. A background worker then dequeues that task and processes it when CPU resources are available.
- Sliding Window Maximum: A classic competitive programming trick utilizing a Monotonic Deque (a double-ended queue that maintains items in a sorted order). It allows developers to find the maximum or minimum value in a "window" sliding over a massive array in strictly linear
O(n)time.
Conclusion: Order from Chaos
If you want to reverse the order of things, you use a Stack. However, if you want to preserve the exact sequence of events, you use a Queue.
By enforcing the simple, real-world logic of an ATM line, the Queue prevents task starvation, manages asynchronous data streams safely, and powers the most essential search algorithms in computer science. It is the fundamental data structure of fairness.


