Queues & Circular Queues
FIFO mechanics, array false-overflow failure mode, modulo arithmetic ring buffers, lock-free queue primitives.
Reinforce your mental model: run, pause, and inspect live pointers with our interactive visualizer:
Topics Covered:
31. Queue Abstract Data Type (FIFO) & Operations • Linear Array Queue & The "False Overflow / Drift" Problem • Linked List Queue Implementation • 32. Circular Queue (Ring Buffer) & Modulo Arithmetic • Wrap-Around State Tracking & Kernel Ring Buffers • Two-Stack Queue Paradigm (Potential Method Proof) • Production Implementations
While stacks govern depth-first backtracking through Last-In, First-Out (LIFO) access, queues enforce fair, sequential scheduling through First-In, First-Out (FIFO) semantics. Elements enter at the rear and exit from the front, mirroring pipeline buffers and operating system task schedulers. However, naive linear array implementations suffer from pointer drift and false capacity exhaustion. This chapter analyzes the FIFO abstraction, diagnoses the false overflow failure mode, formalizes modulo-arithmetic circular ring buffers, details two-stack queue emulation with a rigorous potential method amortized proof, and explores lock-free kernel buffer architectures.
Learning Objectives
#- Define the FIFO Queue Abstract Data Type and enforce boundary invariants for
frontandrearpointers. - Diagnose the "false overflow" (pointer drift) problem in linear array queues and quantify the latency penalty of element shifting.
- Implement circular queues (ring buffers) using modulo arithmetic to achieve wrap-around insertions and deletions.
- Compare full/empty state disambiguation strategies: explicit counter tracking versus the reserved empty slot invariant.
- Prove the amortized complexity of the two-stack queue using the formal Physicist's Potential Method.
- Analyze the architectural role of circular ring buffers in Linux kernel
kfifoand high-throughput network packet ring buffers.
Topic 31: Queue (FIFO) Architecture & The Linear Drift Problem
#1. Conceptual Foundations & The FIFO Invariant
A Queue is a restricted-access linear sequence governed by the First-In, First-Out (FIFO) discipline:
- Elements are inserted strictly at the Rear (Tail) via
Enqueue. - Elements are removed strictly from the Front (Head) via
Dequeue. - The element that has spent the longest duration in the queue is always the next one to be serviced.
Interactive Simulations:
Step through FIFO queue behavior live in the Interactive Queue Enqueue Simulator and the Interactive Queue Dequeue Simulator.
2. The Queue Abstract Data Type (ADT) Interface
#| Operation | Description | Target Time | Auxiliary Space | Invariant / Precondition |
|---|---|---|---|---|
Enqueue(x) | Append item to the Rear | Fails with Overflow if bounded capacity is reached | ||
Dequeue() | Remove and return item at Front | Fails with Underflow if queue is empty | ||
Front() / Peek() | Inspect value at Front without mutating | Requires non-empty queue | ||
Rear() | Inspect value at Rear without mutating | Requires non-empty queue | ||
IsEmpty() | Returns true if element count is zero | Verified via count == 0 or front == -1 | ||
Size() | Returns current active element count | Returns non-negative integer |
3. The Fatal Flaw of Linear Arrays: False Overflow (Pointer Drift)
#Consider a static array of capacity with two pointer offsets: front and rear.
State Progression Demonstrating Drift
| Step | Operation | front | rear | Array Contents | Status & Operational Diagnostics |
|---|---|---|---|---|---|
| 0 | Initial State | -1 | -1 | [ _, _, _, _, _ ] | Queue is empty |
| 1 | Enqueue 5 items () | 0 | 4 | [ 10, 20, 30, 40, 50 ] | Array is legitimately 100% full |
| 2 | Dequeue 3 items () | 3 | 4 | [ _, _, _, 40, 50 ] | Slots 0, 1, 2 vacated and available |
| 3 | Attempt Enqueue(60) | 3 | 4 | [ _, _, _, 40, 50 ] | CRASH: False Overflow! |
Why Linear Arrays Fail for Queues:
The condition rear == capacity - 1 evaluates to true (), so the linear queue reports an Overflow Error and rejects the item, even though of the physical array is vacant!
To reuse the vacated slots at the front of a linear array, the implementation would have to shift all remaining elements back to index 0 on every dequeue:
| Initial Fragmented State | Shift Remediation | Resulting Compact State | Algorithmic Penalty |
|---|---|---|---|
[ _, _, _, 40, 50 ] | Move , | [ 40, 50, _, _, _ ] | data moves per Dequeue (violates contract) |
4. Pointer Drift vs. Circular Ring Buffer Topology
#Topic 32: Circular Queues (Ring Buffers) & Modulo Arithmetic
#1. Conceptual Architecture & The Modulo Ring
Instead of treating backing memory as a finite line that dead-ends at index , a Circular Queue (Ring Buffer) bends the array into a continuous logical ring where index connects directly to index .
When pointer rear or front increments past the physical boundary of the array, it wraps around to the beginning using Modulo Arithmetic:
When slot is reached, . If slot was previously vacated by a Dequeue, rear immediately claims it without shifting a single byte of memory.
2. Disambiguating Full vs. Empty States
#When front == rear, does it signify that the queue is completely empty or completely full? Two distinct architectural patterns resolve this ambiguity:
Strategy A: Explicit Count Variable (Recommended)
Maintain an internal integer count tracking the active element count ():
- Empty Condition:
- Full Condition:
- Available Slots:
Strategy B: Reserved Empty Slot (Classic Textbook)
Sacrifice one array slot permanently. An array of size holds at most elements:
- Empty Condition:
- Full Condition:
3. Step-by-Step Wrap-Around State Trace
#Let Capacity . We trace a complete lifecycle demonstrating wrap-around:
| Step | Operation | front | rear | count | Physical Array | Event / Notes |
|---|---|---|---|---|---|---|
| 0 | Init(5) | 0 | -1 | 0 | [ _, _, _, _, _ ] | Buffer allocated empty |
| 1 | Enqueue(10) | 0 | 0 | 1 | [ 10, _, _, _, _ ] | Standard insert |
| 2 | Enqueue(20) | 0 | 1 | 2 | [ 10, 20, _, _, _ ] | Standard insert |
| 3 | Enqueue(30) | 0 | 2 | 3 | [ 10, 20, 30, _, _ ] | Standard insert |
| 4 | Dequeue() | 1 | 2 | 2 | [ (10), 20, 30, _, _ ] | Slot 0 vacated (front = 1) |
| 5 | Dequeue() | 2 | 2 | 1 | [ (10), (20), 30, _, _ ] | Slot 1 vacated (front = 2) |
| 6 | Enqueue(40) | 2 | 3 | 2 | [ _, _, 30, 40, _ ] | Standard insert |
| 7 | Enqueue(50) | 2 | 4 | 3 | [ _, _, 30, 40, 50 ] | Physical boundary reached |
| 8 | Enqueue(60) | 2 | 0 | 4 | [ 60, _, 30, 40, 50 ] | WRAP-AROUND: ! Reclaims Slot 0! |
| 9 | Enqueue(70) | 2 | 1 | 5 | [ 60, 70, 30, 40, 50 ] | WRAP-AROUND: ! Queue 100% Full! |
| 10 | Enqueue(80) | 2 | 1 | 5 | — | Overflow cleanly rejected! |
4. Two-Stack Queue Implementation & Formal Potential Method Proof
#Can a strict FIFO queue be constructed using only two LIFO stacks ( and )?
Enqueue(x):
S_in.Push(x)
Dequeue():
if S_out.IsEmpty():
if S_in.IsEmpty(): raise Underflow
while not S_in.IsEmpty():
S_out.Push(S_in.Pop()) // Inverts LIFO to FIFO order!
return S_out.Pop()
Formal Amortized Proof via the Physicist's Potential Method:
Define the potential function of the two-stack system at state as:
- Notice that (initially empty) and for all .
Amortized Cost of
Enqueue(x):- Actual work (pushing onto ).
- .
- Amortized cost:
Amortized Cost of
Dequeue():- Case A: is non-empty:
- Actual work (pop from ).
- (size of is unchanged).
- .
- Case B: is empty (batch transfer of items):
- Actual work ( pops from , pushes to , plus final pop).
- .
- Amortized cost:
- Case A: is non-empty:
Therefore, every operation executes in amortized time.
5. Systems Engineering: Kernel Ring Buffers & Bitwise Masking
#In high-performance operating system engineering (such as the Linux kernel's kfifo subsystem):
- Capacities are constrained to powers of two: .
- Integer modulo division (
id % C) translates to an expensive multi-cycle CPU instruction (idivon x86, taking 15–40 clock cycles). - By constraining , modulo is replaced with a single-cycle bitwise AND mask:
// Linux kernel kfifo wrap-around idiom:
unsigned int next_in = (fifo->in + 1) & (fifo->size - 1);Furthermore, by decoupling in and out into 64-bit monotonically increasing unsigned counters that wrap naturally on integer overflow (), Single-Producer Single-Consumer (SPSC) ring buffers run completely lock-free without mutex locks or atomic compare-and-swap (CAS) instructions.
6. Production Multi-Language Implementations
#A. C++20 Ring Buffer with Bitwise Masking & Template Safety
#include <iostream>
#include <vector>
#include <stdexcept>
#include <concepts>
template <typename T>
class CircularQueue {
private:
std::vector<T> buffer_;
size_t capacity_;
size_t mask_;
size_t front_;
size_t rear_;
size_t count_;
static size_t next_power_of_two(size_t n) {
size_t power = 1;
while (power < n) power <<= 1;
return power;
}
public:
explicit CircularQueue(size_t min_capacity = 8)
: capacity_(next_power_of_two(min_capacity)),
mask_(capacity_ - 1),
buffer_(capacity_),
front_(0),
rear_(0),
count_(0) {}
bool enqueue(T item) {
if (is_full()) {
return false;
}
buffer_[rear_] = std::move(item);
rear_ = (rear_ + 1) & mask_;
++count_;
return true;
}
bool dequeue(T& item) {
if (is_empty()) {
return false;
}
item = std::move(buffer_[front_]);
front_ = (front_ + 1) & mask_;
--count_;
return true;
}
[[nodiscard]] bool is_empty() const noexcept { return count_ == 0; }
[[nodiscard]] bool is_full() const noexcept { return count_ == capacity_; }
[[nodiscard]] size_t size() const noexcept { return count_; }
[[nodiscard]] size_t capacity() const noexcept { return capacity_; }
};7. Key Takeaways
#- FIFO Invariant: Queues enforce First-In, First-Out order, serving as the universal primitive for breadth-first search and pipeline scheduling.
- False Overflow Elimination: Naive linear array queues suffer from pointer drift; circular ring buffers recycle memory via modulo arithmetic
(idx + 1) % C. - Disambiguation Rules: Full versus empty state in ring buffers is cleanly resolved by maintaining an explicit element
counttracker. - Hardware Symbiosis: Sizing ring buffers to powers of two enables bitwise wrap-around masking
idx & (C - 1), a cornerstone optimization in Linux kernelkfifoand NIC ring buffers.
Academic Attribution & References
#- Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2022). Introduction to Algorithms (4th ed.), Chapter 10: Elementary Data Structures. MIT Press.
- Corbet, J., Rubini, A., & Kroah-Hartman, G. (2005). Linux Device Drivers (3rd ed.), Chapter 11: Data Types in the Kernel (kfifo). O'Reilly Media.
- Sedgewick, R., & Wayne, K. (2011). Algorithms (4th ed.), Section 1.3: Bags, Queues, and Stacks. Addison-Wesley.
- Knuth, D. E. (1997). The Art of Computer Programming, Volume 1: Fundamental Algorithms (3rd ed.), Section 2.2: Linear Lists. Addison-Wesley.