3.2.7 · D2Linear Data Structures

Visual walkthrough — Queue — FIFO semantics, enqueue - dequeue, circular array implementation

2,082 words9 min readBack to topic

We derive the whole circular queue from zero. No prior formula assumed.


Step 0 — The words we will use

Before any symbol, plain meanings. Keep these five in your head:

Everything below is made of only these five ideas.


Step 1 — Lay out the boxes, put the front at the left

WHAT. Draw an empty array of boxes, numbered to . Nothing is inside yet, so and we park .

WHY. We need a fixed reference point. front is our only memory of "where the line begins." We start it at purely by convention — any index would do, but is tidy.

PICTURE. The boxes are empty. The green arrow labelled front sits under box . No coral (data) anywhere yet.

Figure — Queue — FIFO semantics, enqueue - dequeue, circular array implementation

  • — the running tally of elements. Here it is .
  • The double arrow means "these two statements are the same thing." An empty queue is exactly the case — no other test needed.

Step 2 — Enqueue: where does a new element go?

WHAT. We enqueue(A). The element must land in the box just after the last occupied box. With nothing inside, the "box just after front" is front itself.

WHY. The elements always sit in a solid block starting at front. If there are of them starting at front, the next free slot is steps past front. Right now , so the next slot is .

PICTURE. Box turns coral, labelled A. front still points at box . A new dashed arrow, rear, marks "the next free slot," which after placing A now sits at box .

Figure — Queue — FIFO semantics, enqueue - dequeue, circular array implementation

  • — where the block starts ().
  • — how long the block is (was , so rear was ; after adding A, so rear ).
  • — the index of the next free box, i.e. where the next enqueue will write.

No modulo yet — nothing has fallen off the end. We add it exactly when we need it, in Step 5.


Step 3 — Fill up, watch the block grow rightward

WHAT. Enqueue B, then C. Each time: write at rear, then grows by .

WHY. Every enqueue lengthens the block by one, so the "next free slot" slides one box to the right each time. front never moves during enqueue — the oldest element (A) is untouched.

PICTURE. Boxes are coral: A, B, C. front stays at ; rear has walked to box . The occupied block is highlighted in mint.

Figure — Queue — FIFO semantics, enqueue - dequeue, circular array implementation

  • — unchanged; enqueue never touches the front.
  • — three elements A, B, C.
  • — the free box where a fourth element would go.

Step 4 — Dequeue: the front walks forward, a gap opens on the left

WHAT. dequeue() twice. Each removes the element at front (returning A, then B), then moves front one box to the right.

WHY. FIFO = the oldest leaves first. The oldest is always at front. After handing it out, the new oldest is the box one step right, so front advances by . Crucially, shrinks by — so the rear formula still balances.

PICTURE. Boxes are now empty (a grey "wasted" gap on the left). front points at box (C). Notice the empty boxes on the left — this is the seed of the whole problem.

Figure — Queue — FIFO semantics, enqueue - dequeue, circular array implementation

  • The arrow means "gets updated to."
  • After two dequeues: , (only C remains), so still.

Step 5 — The wrap: glue box to box with modulo

WHAT. Now , (only C, at box ). Enqueue D, then E. The first goes to box . But the next rear wants to be box ... then box , which does not exist. We wrap it back to box .

WHY THIS TOOL — the modulo. We need an operation that takes any index and folds it back into the valid range , turning the straight row into a ring. That operation is modulo, written . Why modulo and not, say, an if index == N: index = 0? Because modulo handles every overshoot in one stroke — even jumps of more than one — and is the exact mathematical statement of "positions on a ring of size ." answers the question: "if I walk steps around a ring of boxes starting at , which box do I land on?"

  • — the un-wrapped target: how far past box we would be on an infinite row.
  • — folds that number back onto the ring of boxes. E.g. ; ← the wrap.
  • — the real box index, always in .

PICTURE. The array is redrawn as an actual ring of boxes. Box sits next to box . D lands in box ; E wraps around into box . front (green) at box , rear sweeping to box . The logical order following the ring clockwise is C → D → E.

Figure — Queue — FIFO semantics, enqueue - dequeue, circular array implementation

Let's verify the wrap with a tiny ring (the parent's Example 2): , , holding A at box , B at box . Enqueue C: So C lands in box — physically at the "start," logically at the "end." Impossible to get right without .


Step 6 — dequeue also wraps, for the same reason

WHAT. When front itself reaches the last box and we dequeue, it too must jump back to box .

WHY. front walks the same ring. The instant it would step off the right edge it must reappear at — identical logic to rear. So the update carries the same .

PICTURE. front is at box (last box). One dequeue: green arrow leaps across the glued seam to box .

Figure — Queue — FIFO semantics, enqueue - dequeue, circular array implementation

  • — step one box clockwise.
  • — if that was box (off the edge), fold it to box .

Example on : . ✅


Step 7 — The degenerate twins: full and empty look identical (without count)

WHAT. Consider a ring with only a front and a rear index and no count. Empty: . Now fill all boxes: rear wraps all the way back to . Both give .

WHY IT BREAKS. The single test is being asked to mean two opposite things at once — a full ring and an empty ring produce the identical picture. A test that cannot tell a full room from an empty room is useless.

PICTURE. Two rings side by side: left one all-grey (empty), right one all-coral (full). Both have front and rear arrows stacked on the same box. Same pointers, opposite meaning.

Figure — Queue — FIFO semantics, enqueue - dequeue, circular array implementation

THE FIX — track count, and the ambiguity vanishes:

  • — zero elements. Unmistakably empty.
  • — all boxes used. Unmistakably full.

Because (for any real capacity ), these two states can never be confused. This is why the reference code carries count instead of a rear index.


The one-picture summary

Everything on one canvas: the ring of boxes, front (green) pointing at the oldest element, the occupied block sweeping clockwise, rear (dashed) marking the next free box, and the two master formulas glued to the seam where the wrap happens.

Figure — Queue — FIFO semantics, enqueue - dequeue, circular array implementation

This structure is the beating heart of a ring buffer, powers the frontier in Breadth-First Search (BFS), and generalises to the deque and priority queue. Its per-operation cost is a flat — see Amortized Analysis for why "no shifting, ever" matters.

Recall Feynman: retell the whole walkthrough

We started with a straight row of numbered boxes and said "the front of the line is at box front, and there are count people waiting." Adding a person puts them in the box count steps past the front — that box is called rear. Removing a person hands out whoever is at front, then slides front one box right and drops count by one. Do this many times and the line drifts to the right, leaving empty boxes wasted on the left. To fix that, we bent the row into a ring by gluing the last box to the first: whenever an index would walk off the right edge, mod N folds it back to box . Finally, because a full ring and an empty ring both make front and rear land on the same box, we keep count around: means empty, means full — never a mix-up. Two formulas, one ring, all O(1).


Active Recall

Where does a new element land relative to front?
At index — count steps clockwise from front.
What does do geometrically?
Folds an index back onto a ring of boxes, so walking off the right edge reappears at box .
Why keep count instead of a rear index?
Because a full ring and an empty ring both give front == rear; count ( vs ) disambiguates them.
New front after a dequeue on the last box (N=5, front=4)?
— it wraps to box .