Worked examples — Deque (double-ended queue) — operations, use cases
Before anything else, three words we will lean on constantly — defined in plain English so no symbol sneaks in unearned:
The picture above is our mental model for the array deque: a ring of slots. front is an arrow pointing at the first live slot; the live region sweeps clockwise to rear. Nothing ever "shifts" — only the arrows move.
The scenario matrix
Every deque exercise falls into one of these cells. Our examples below are labelled with the cell they cover so you can see the whole space is filled.
| # | Cell (case class) | What makes it tricky |
|---|---|---|
| A | Plain trace, both ends | keeping front→back order straight |
| B | Circular index, no wrap | when nothing crosses the edge |
| C | Circular index, front wraps left (the guard) | push_front when front = 0 |
| D | Circular index, rear wraps right | push_back crossing slot |
| E | Degenerate: empty deque | popping when size = 0 (error/underflow) |
| F | Degenerate: single element | front == rear, both pops target it |
| G | Full deque → resize (amortized doubling) | copy into a fresh, un-wrapped array |
| H | Word problem — palindrome (peel both ends) | loop condition size > 1 |
| I | Exam twist — Sliding Window Maximum | monotonic deque of indices |
| J | Exam twist — 0-1 BFS | which end for weight 0 vs weight 1 |
We now hit every cell.
Example 1 — Cell A: plain both-ends trace
Steps:
push_back(7)→[7]. Why this step? First element; front and rear are both7.push_front(4)→[4, 7]. Why?push_frontinserts before everything, so4lands left of7.push_back(9)→[4, 7, 9]. Why?push_backappends on the right.push_front(1)→[1, 4, 7, 9]. Why? Again inserts at the far left.pop_back()removes the rightmost → returns 9, leaves[1, 4, 7].pop_front()removes the leftmost → returns 1, leaves[4, 7].
Verify: count check — 4 pushes minus 2 pops = 2 elements remaining, matches
[4, 7]. The two survivors4and7were the middle insertions, exactly the elements furthest from both ends. ✓
Example 2 — Cell B: circular index, no wrap
- Recall the formula from the parent: the -th element from the front sits at . Why this formula? The live region marches to the right by one slot per step; only matters if we cross the edge.
- Plug in: . Why no wrap? , so we never reach the edge; the leaves it untouched.
Verify: slot 3 is indeed
R, the 3rd live element from the front. Direct read-off agrees. ✓
Example 3 — Cell C: push_front wraps left (the guard)
- Move front left: .
Why the ?
push_frontneeds a fresh slot to the left of the current front. - Compute: . Why the ? Without it we'd have , an illegal index. Adding before the turns "one step left of slot 0" into "slot ", the wrap the ring picture demands (look at the red arrow in the figure jumping from slot 0 to slot 4).
- Store
Zat slot 4, setsize = 3.
Verify: array becomes
[A, B, _, _, Z], and reading from the newfront=4gives logical orderZ, A, B. The naive would have crashed; rescued it into the valid range . ✓
Example 4 — Cell D: push_back wraps right
- The next free slot at the rear is one past the last live slot. The last live slot is the -th from front: . The new slot is one further, i.e. . Why ? Front plus how many live elements = the first empty slot on the rear side.
- Compute: . Why does it wrap? equals the edge value ; the remainder sends us back to the very first physical slot (see the mint arrow curving from slot 4 to slot 0 in the figure).
- Store
Kat slot 0, setsize = 3.
Verify: array
[K, _, _, M, N], reading fromfront=3: logical orderM, N, K.Kcorrectly sits at the rear even though it physically wrapped to slot 0. ✓
Example 5 — Cell E: degenerate empty (underflow)
- Check the guard before touching any index: if
size == 0, there is no front. Why check first? If you computed an index like you'd read a stale slot — old data still physically sitting there. That would return a ghost value. - Signal underflow: raise an error / return a sentinel (e.g.
None), and do not decrementsizebelow 0. Why not decrement?size = -1corrupts every later index computation.
Verify: invariant
0 ≤ size ≤ Cmust always hold. Popping an empty deque with a guard keepssize = 0; without the guard it drops to-1, breaking the invariant. The guard is the fix. ✓
Example 6 — Cell F: single element (front == rear)
- Rear index . Why? With a single element the rear is the front — both point at slot 2.
- Return the value at slot 2 =
S; setsize = 0. Why not movefront?pop_backremoves from the rear, sofrontstays; onlysizeshrinks. (Had we donepop_front, we'd advancefrontinstead and also reachsize = 0.)
Verify: after the pop,
size = 0→ the empty case of Example 5. Front and rear formulas both gave slot 2, confirming they coincide for one element. ✓
Example 7 — Cell G: full deque → resize (amortized )
- Detect full:
size == C(4 == 4). Must grow. Why grow now? Every physical slot is live; there is nowhere to pute. - Allocate a new array of capacity . Why double, not ? Doubling makes copies rare: pushes trigger only resizes whose copy costs sum to , so the amortized per-push cost is (see Dynamic Array Amortized Analysis).
- Copy in logical order, starting the new front at 0: read old logical order
a, b, c, dand place at new slots 0,1,2,3. Set newfront = 0. Why re-lay flat? The copy is the perfect moment to un-wrap the ring, so the new array is contiguous and simple. - Now
push_back(e): new rear slot . Storee,size = 5.
Verify: new array
[a, b, c, d, e, _, _, _],front = 0,size = 5, capacity8. Logical ordera, b, c, d, eis preserved and no data was lost. ✓
Example 8 — Cell H: word problem, palindrome peel
- Deque:
[L, E, V, E, L],size = 5. Loop whilesize > 1. Why> 1? A single leftover middle character has nothing to compare against — it's automatically fine. - Compare
pop_front()vspop_back():LvsL→ equal. Now[E, V, E],size = 3. - Compare again:
EvsE→ equal. Now[V],size = 1. - Loop stops (
size = 1). All comparisons matched → palindrome. Why a deque? You need to grab the outermost pair each time — front and rear together — which only a deque does in per side.
Verify: length 5 ⇒ comparisons expected; we did exactly 2. String reversed is
LEVEL= original, confirming palindrome. ✓
Example 9 — Cell I: exam twist, Sliding Window Maximum
We hold indices; the front index always points to the current window's max. Two rules each step :
- Pop from back while the value at the back index the new value (those can never be the max again).
- Pop from front if it slid out of the window (front index ).
- (val 1): deque
[0]. - (val 3): back is idx0 (val 1) → pop it; push 1 →
[1]. - (val -1): back is idx1 (val 3) → keep; push 2 →
[1,2]. Window complete → max = value at front idx1 = 3. Why front? Front holds the largest index-value by construction. - (val -3): back idx2 (val -1) → keep; push 3 →
[1,2,3]. Front idx1 = ? No, , idx1 stays. Window → max = value at idx1 = 3. - (val 5): pop back while : idx3(-3), idx2(-1), idx1(3) all → all popped; push 4 →
[4]. Front idx4 = i-k? , idx4 stays. Window → max = idx4 = 5. - (val 3): back idx4 (val 5) → keep; push 5 →
[4,5]. Window → max = front idx4 value = 5.
Verify: brute force windows: , , , . Deque produced
[3, 3, 5, 5]. Match. Each index is pushed once and popped once → total work . ✓ (See Sliding Window Maximum.)
Example 10 — Cell J: exam twist, 0-1 BFS end choice
- Pop
Sfrom the front (distance 0). Relax its edges. Why a deque, not a Priority Queue? With only two possible weights, we never need a full heap — the deque keeps distances sorted for free. - Weight-0 edge to
A:push_front(A)(its distance equalsS's, so it belongs at the same front level). Why front? A 0-weight move doesn't increase distance, soAmust be processed before anything one step farther. - Weight-1 edge to
B:push_back(B)(distance is one more, process later). Why back? Larger distance ⇒ later ⇒ the rear. - Deque now front→back:
[A, B]. Next pop from front →A.
Verify: final distances , . The deque
[A, B]is in nondecreasing distance order (0 then 1), which is exactly the invariant 0-1 BFS needs; popping the front yieldsA(distance 0) first. ✓ (See 0-1 BFS.)
Coverage check
Recall Did we hit every cell?
A→Ex1, B→Ex2, C→Ex3, D→Ex4, E→Ex5, F→Ex6, G→Ex7, H→Ex8, I→Ex9, J→Ex10. Every row of the matrix has a fully worked, verified example.
Cell A covered?
Which cells test modular wrap and which direction?
Which example enforces the size invariant on underflow?
Why does the resize example re-lay the array flat?
Connections
- Parent (Hinglish) — the concept these examples drill
- Stack · Queue — restricted deques the traces mimic
- Circular Buffer / Ring Buffer — the ring picture behind Cells B–D, G
- Doubly Linked List — alternate backing
- Dynamic Array Amortized Analysis — why doubling in Example 7 is
- Sliding Window Maximum — Example 9
- 0-1 BFS — Example 10
- Priority Queue — what the deque replaces in 0-1 BFS