Exercises — Queue — FIFO semantics, enqueue - dequeue, circular array implementation
Before we start, one picture to keep in your head the whole way down: the array is a ring of fixed size. Throughout this page, = the array's capacity (the total number of slots — the ring has exactly chairs, numbered to ). Only two numbers ever move: front (the index where the oldest element sits) and count (how many elements are seated). The rear (next free slot) is derived, never stored.

What this figure shows (read before continuing): six array slots (indices 0–5, so ) bent into a ring so the last slot touches the first. Three elements A, B, C occupy indices 2, 3, 4 (blue). The yellow arrow marks front (index 2 — the oldest element, A, leaves next). The green arrow marks rear, the next free slot, which sits just past the last occupied slot. The red centre label is count = 3, the number of seated elements. Notice rear is not stored anywhere on the ring — it is computed from front and count, which is exactly the formula we derive next.
Deriving rear from a picture (do not memorize it — see it)
The single formula everything below leans on:
Level 1 — Recognition
Exercise 1.1 (L1)
A structure removes the element that has been waiting the longest. New elements join at the back. Is this a stack or a queue? What is the acronym for its discipline?
Recall Solution 1.1
Removing the oldest is FIFO — First In, First Out. This is a queue. (A stack would remove the newest — LIFO.)
Exercise 1.2 (L1)
For a circular queue with capacity , front = 4, count = 0. Is the queue empty, full, or neither?
Recall Solution 1.2
The test is count, not the value of front. Since , the queue is empty — regardless of where front happens to point. (This is exactly why we store count: front alone tells you nothing about occupancy.)
Exercise 1.3 (L1)
Name the single arithmetic operation that lets an index "fall off the end" of the array and reappear at index 0.
Recall Solution 1.3
The modulo (remainder) operation: . When , the remainder is , wrapping back to the start.
Level 2 — Application
Exercise 2.1 (L2)
Capacity , front = 3, count = 4. Compute the rear (next free slot index).
Recall Solution 2.1
The queue occupies indices (four wrapping slots starting at front), so the next free slot is index 2. ✅
Exercise 2.2 (L2)
Capacity . Start empty (front = 0, count = 0). Perform, in order:
enqueue(P), enqueue(Q), dequeue(), enqueue(R), enqueue(S).
Give the final front, count, and the array contents.
Recall Solution 2.2
Track it step by step. rear is always . (In the table below, is written out consistently as the mathematical remainder — the same operation your code writes as %.)
| step | rear used | front | count | array |
|---|---|---|---|---|
| enq P | 0 | 1 | [P,,,_] | |
| enq Q | 0 | 2 | [P,Q,,] | |
| deq | — (read idx 0) | 1 | 1 | [,Q,,_], returned P |
| enq R | 1 | 2 | [,Q,R,] | |
| enq S | 1 | 3 | [_,Q,R,S] |
Note on the array cells: after dequeue() we advance front but do not erase the old slot (its stale value is simply out of the live range). So physically index 0 still holds P — it is just no longer part of the logical queue. The logical contents are the live slots 1, 2, 3.
Final: front = 1, count = 3, logical front→rear order is Q, R, S — FIFO preserved (P left first). ✅
Exercise 2.3 (L2)
Capacity , front = 2, count = 2 (holds elements at indices 2 and 0). You call enqueue(Z). Which physical index does Z land in, and what are the new front / count?
Recall Solution 2.3
. So Z goes to index 1.
After enqueue, front is unchanged (2), count becomes 3. The queue is now full (). ✅
Level 3 — Analysis
Exercise 3.1 (L3)
A student uses only two indices, front and rear, and no count. With they enqueue 4 elements into an empty queue. They then test emptiness with front == rear. What does the test report, and why is that catastrophic?
Recall Solution 3.1
Empty start: front = rear = 0. Each enqueue writes at rear then sets rear = (rear+1) \bmod 4. After 4 enqueues: rear = (0+4) \bmod 4 = 0. So now front = rear = 0 again — identical to the empty state.
The test reports "empty" on a completely full queue. Catastrophic: a dequeue guarded by "if not empty" would refuse to run, or worse, a naive loop treats a full buffer as done and drops all 4 elements. This is precisely the ambiguity count was invented to kill. ✅
Exercise 3.2 (L3)
The "sacrifice one slot" scheme fixes the ambiguity without a count: it declares isFull ⟺ (rear + 1) % N == front, keeping one slot permanently empty. With , how many elements can this queue actually hold? Compare against the count-based design.
Recall Solution 3.2
With one slot always reserved as a gap, usable capacity is elements.
Why: full triggers when advancing rear would collide with front, i.e. when 4 slots are used and one gap remains. The count-based design has no gap and holds all 5. Trade-off: the slot-sacrifice avoids storing a counter (saves one integer, avoids count bookkeeping) but wastes one array cell. The count design uses full capacity at the cost of maintaining the counter. ✅
Exercise 3.3 (L3)
Explain in one sentence why each of enqueue and dequeue is in the circular array, contrasting with the "shift-left on dequeue" fix.
Recall Solution 3.3
Circular enqueue/dequeue only compute an index and move one counter — a fixed amount of work regardless of how many elements are stored, hence . The shift-left fix physically moves every remaining element one slot left on each dequeue, which is work proportional to queue size. The ring avoids all movement by moving indices instead of data. ✅
Level 4 — Synthesis
Exercise 4.1 (L4)
Design (in pseudocode) a dequeue() that returns the element and shrinks the array to half capacity when the queue becomes at most full, to save memory. State the resulting amortized cost per dequeue and why it isn't every time.
Recall Solution 4.1
def dequeue(self):
if self.count == 0: raise IndexError("empty")
x = self.arr[self.front]
self.front = (self.front + 1) % self.N
self.count -= 1
# shrink only when very sparse, and never below some floor
if self.N > 4 and self.count <= self.N // 4:
self._resize(self.N // 2) # copy count elements in FIFO order
return x_resize(m) allocates a new array of size m, copies the count live elements starting at logical front into slots 0..count-1, then sets front = 0. That copy is .
Amortized cost: a resize costs , but after halving you must dequeue roughly more elements before you can shrink again — so the expensive copy is "paid off" over cheap operations. Averaged out, each dequeue is amortized. The threshold (not ) creates a hysteresis gap that prevents shrink/grow thrashing at the boundary. This is the classic argument of Amortized Analysis. ✅
Exercise 4.2 (L4)
Breadth-First Search (BFS) uses a queue. On the small graph below (edges: 1–2, 1–3, 2–4, 3–4), run BFS from node 1, enqueueing neighbours in ascending order. List the visit order and show the queue's contents right after node 1 is dequeued.

What this figure shows: four nodes (1 at top, 2 and 3 in the middle layer, 4 at the bottom) with edges 1–2, 1–3, 2–4, 3–4. Colours encode BFS visit order — node 1 (red) is visited first, nodes 2 and 3 (yellow, the distance-1 layer) next, node 4 (green, the distance-2 layer) last. The picture is the point of the exercise: FIFO forces the whole yellow layer to finish before the green one begins.
Recall Solution 4.2
BFS: enqueue start, then repeatedly dequeue a node, mark visited, enqueue its unvisited neighbours.
- Start: enqueue 1 → queue
[1]. - Dequeue 1, visit it. Neighbours 2, 3 (ascending) → enqueue both. Queue right after dequeuing 1:
[2, 3]. - Dequeue 2, visit. Neighbour 4 unvisited → enqueue. Queue
[3, 4]. - Dequeue 3, visit. Neighbour 4 already queued/visited → skip. Queue
[4]. - Dequeue 4, visit. No new nodes. Queue
[].
Visit order: 1, 2, 3, 4. FIFO is what guarantees we finish all distance-1 nodes (2, 3) before any distance-2 node (4) — that layer-by-layer sweep is the whole point of using a queue instead of a stack. ✅
Level 5 — Mastery
Backward wrapping: the sign edge case (read before the exercises)
Every operation so far moved an index forward (add 1, wrap with ). Some operations — most importantly push_front on a deque — must move an index backward (subtract 1). Backward moves hide a trap that forward moves never do, so we derive the safe rule from scratch here.
Exercise 5.1 (L5)
You are given a CircularQueue but you have lost the stored rear; you only kept the array snapshot, , front, and count. Prove that the entire logical sequence (front → rear order) is fully recoverable, and write the index of the -th element ( is the front) as a formula. Then apply it: , front = 4, count = 5, list the physical indices in front-to-rear order.
Recall Solution 5.1
Claim: the -th element from the front sits at physical index
Proof: the queue occupies exactly count consecutive slots on the ring, beginning at front, in FIFO order. "Consecutive on a ring" means each next slot is the previous taken . Starting at front and stepping times gives . Since ranges over we generate exactly the occupied slots, in order — no gaps, no repeats (because guarantees the values don't wrap onto each other). Hence the whole sequence is recovered from front and count alone; rear was always redundant. ∎
Apply : Front-to-rear physical indices: 4, 5, 0, 1, 2. (The one free slot is index 3, which equals the derived — consistent.) ✅
Exercise 5.2 (L5)
A deque adds push_front(x) (insert at the front). Derive the physical index this must write to, in terms of front and , and explain why a subtraction here still needs the modulo. Apply: , front = 0, count = 2, do push_front(Q).
Recall Solution 5.2
To insert before the current front, the new front index is one step backward on the ring:
We write at new_front, set front = new_front, and count += 1.
Why modulo is still needed: stepping backward from index 0 gives , which is not a valid slot. On the ring, "one before slot 0" is the last slot, . The modulo maps (using the non-negative-remainder convention derived in the edge-case box above), and the /outer- form makes this safe even in languages whose % returns negatives.
Apply : . So lands at index 4, front becomes 4, count becomes 3. This connects directly to the Circular Buffer / Ring Buffer and the general two-ended ring. ✅
Exercise 5.3 (L5)
Contrast a plain FIFO queue with a Priority Queue on this task: "process 3 arrives, then process 1 (higher priority = lower number), then process 2." A FIFO queue and a priority queue each dequeue three times. Give both output orders and state which structure the circular array natively implements.
Recall Solution 5.3
- FIFO queue: dequeues in arrival order → 3, 1, 2. The circular array implements exactly this — position on the ring encodes arrival time, nothing else.
- Priority queue: dequeues by priority (smallest number first) → 1, 2, 3. This needs an ordering structure (typically a heap), not arrival order.
The circular array natively implements the FIFO queue, not the priority queue: its dequeue is hard-wired to the oldest arrival at arr[front], with no notion of priority. To get priority you replace the ring with a heap. ✅
Recall One-screen recap of every formula used
Rear (next free) ::: -th element's index ::: After dequeue ::: Push-front (deque), safe backward wrap ::: Empty / Full ::: / Usable slots with one-slot-sacrifice :::
Related: Parent topic · Modular Arithmetic · Amortized Analysis · Breadth-First Search (BFS) · Deque — Double-Ended Queue · Circular Buffer / Ring Buffer · Priority Queue · Stack — LIFO