Queues & Circular Queues
Understand FIFO order, find the dead space a linear array wastes, and watch the modulo turn that same array into a ring.
- 01Cover
- 02Why a Queue?
- 03Stack vs. Queue
- 04The Blueprint
- 05Simple Queue
- 06The Limitation
- 07Circular Queue
- 08Wrap-Around Trace
- 09Queue via Linked List
- 10The Ring
- 11The Queue Family
- 12std::queue
- 13std::priority_queue
- 14Edge Cases
- 15Complexity Reference
- 16Complete Walkthrough
- 17Predict the Output
- 18Key Takeaways
Why a Queue?
A queue decouples the producer from the consumer. Work often arrives faster than it can be served, so it waits in line — and it waits in arrival order.
Stack vs. Queue
One end does everything. Both operations work on the same side, so the newest element leaves first and the oldest is buried underneath.
Two ends, one job each. Arrivals land at the rear, departures leave from the front, and the order in between is never touched.
The Queue Blueprint
int / Node*int / Node*O(1)O(1)O(1)O(1)front == rear == -1front == rearrear == SIZE - 1rear + 1 == frontSimple Queue (Array)
items[rear] = elementFirst element: an empty queue has no front yet, so the first arrival takes cell 0.
Arrival: the rear walks forward with every element that joins the queue.
Departure: the front walks forward too — and it never walks back.
The guard: once the rear reaches the last cell, the queue refuses new work.
The Limitation
The front only ever moves forward. Every cell it leaves behind stays occupied for good — and the queue still calls itself full while those cells sit there, unused.
Two cells are lost: each dequeue frees a cell logically but leaves the value in memory. The queue can never reach them again.
The queue turns work away while two cells sit empty at the head of the array — unusable, because the front never walks back.
Circular Queue (Array)
Nothing queued: both pointers sit at -1 and the array is untouched.
The rear is one step behind the front: the queue has wrapped around the array — and this shape is full too.
Every cell in play: the queue is full only when no cell is left unused.
One operator: the increment that stopped at the end now wraps around.
The Wrap-Around
Drain the front: front walks to cell 2, and cells 0 and 1 become dead space.
The rear leaves the end of the array and comes back to cell 0.
The dead cell becomes live again — 60 lands exactly where 10 used to be.
The queue is full once more, with five elements and no wasted cell.
Queue via Linked List
Nothing queued: both pointers are null, so the chain has no nodes at all.
Link forward: the old rear points at the new node — no element is ever copied.
Advance the rear: the new node becomes the last element in the queue.
Advance the front, then delete temp frees the node that left.
Circular Queue (Linked List)
In a circular linked list the last node points back at the first. The front of the queue is simply rear->next — so one pointer describes the whole structure.
One node, pointing at itself: the empty queue becomes a one-element ring.
Link to the front: the new node is spliced in between the rear and the old front.
Close the ring: the old rear now points at the new node, which becomes the rear.
Bypass the front: the rear points past the departing node, and it is freed.
rear->next is always the element that leaves next.The Queue Family
delete per departure.enqueue, dequeue and peek identically. Only the storage and the price of memory differ.The STL std::queue
std::deque by default — a chain of fixed-size blocks.Add at the rear. The name changed from enqueue to push, the position did not.
Remove from the front. pop discards the element — read it with front first.
Look without removing: the first element and the last element are both one call away.
New front of line: Bob
new and no delete in this program. The container owns its memory and frees it when it goes out of scope.The STL std::priority_queue
push and pop are O(log n), where a plain queue is O(1).Critical Edge Cases
Every implementation needs the same guards: an empty queue, a queue that has just drained, a queue that is genuinely full, and a node that has already been freed.
if (isEmpty()) return;front = rear = -1;rear == SIZE - 1rear + 1 == frontrear->next = temp->nexttop() on an empty priority queue is undefined.if (!tasks.empty())Complexity Reference
Complete Walkthrough
| Call | Queue after | Output printed |
|---|---|---|
| enqueue(10) | 10 | Enqueued: 10 |
| enqueue(20) | 10 20 | Enqueued: 20 |
| enqueue(30) | 10 20 30 | Enqueued: 30 |
| enqueue(40) | 10 20 30 40 | Enqueued: 40 |
| enqueue(50) | 10 20 30 40 50 | Enqueued: 50 |
| display() | 10 20 30 40 50 | Queue elements: 10 20 30 40 50 |
| dequeue() | 20 30 40 50 | Dequeued: 10 |
| dequeue() | 30 40 50 | Dequeued: 20 |
| display() | 30 40 50 | Queue elements: 30 40 50 |
| enqueue(60) | 30 40 50 60 | Enqueued: 60 |
| enqueue(70) | 30 40 50 60 70 | Enqueued: 70 |
| display() | 30 40 50 60 70 | Queue elements: 30 40 50 60 70 |
| peek() | unchanged | Front element: 30 |
Enqueued: 10
Enqueued: 20
Enqueued: 30
Enqueued: 40
Enqueued: 50
Queue elements: 10 20 30 40 50
=== Dequeue Twice ===
Dequeued: 10
Dequeued: 20
Queue elements: 30 40 50
=== Wrap-Around ===
Enqueued: 60
Enqueued: 70
Queue elements: 30 40 50 60 70
=== Peek ===
Front element: 30
Predict the Output
Trace the four calls by hand before revealing the answer. Use only the rules already covered — especially what the modulo does at the end of the array.
30 40 5030 40 50 6030 40 50 60 70Key Takeaways
(rear + 1) % C_SIZE turns the array into a ring.rear->next.std::queue for FIFO, std::priority_queue for best-first.