LeetCode Β· #622

Design Circular Queue Solution

Design a fixed-size queue that supports O(1) front/rear access and wraparound insertion and deletion without shifting elements.

01
Section One Β· Problem

Problem Statement

🏷️

Difficulty

Medium

πŸ”—

LeetCode

Problem #622

πŸ—οΈ

Pattern

Design β€” ring buffer with modulo arithmetic

Implement a class MyCircularQueue with capacity k. The queue must support enQueue, deQueue, Front, Rear, isEmpty, and isFull. The challenge is distinguishing full from empty while reusing array slots after deletions.

Example
Input: MyCircularQueue q = new MyCircularQueue(3) q.enQueue(1) // true q.enQueue(2) // true q.enQueue(3) // true q.enQueue(4) // false β€” full q.Rear() // 3 q.isFull() // true q.deQueue() // true q.enQueue(4) // true β€” wraps into freed slot q.Rear() // 4
Constraints
β€’ 1 ≀ k ≀ 1000 β€’ 0 ≀ value ≀ 1000 β€’ At most 3000 calls are made to enQueue, deQueue, Front, Rear, isEmpty, and isFull
02
Section Two Β· Brute Force

Shifting Array Queue β€” O(n) deQueue

Store elements from index 0 to size - 1. enQueue writes at the end, but deQueue shifts every remaining element one step left. This satisfies the interface, but it throws away the entire point of a circular queue.

Why it works

Because the logical front is always kept at index 0, Front() and Rear() are trivial. Capacity checks also become easy: full means size == k, empty means size == 0.

Why we can do better
Problem: Shifting makes every deletion O(n). The whole design problem exists to avoid moving data. A ring buffer keeps elements in place and moves only the metadata: head and size.
Java β€” shifting array
class MyCircularQueue { private final int[] data; private int size = 0; public MyCircularQueue(int k) { data = new int[k]; } public boolean enQueue(int value) { if (isFull()) return false; data[size++] = value; return true; } public boolean deQueue() { if (isEmpty()) return false; for (int i = 1; i < size; i++) data[i - 1] = data[i]; size--; return true; } public int Front() { return isEmpty() ? -1 : data[0]; } public int Rear() { return isEmpty() ? -1 : data[size - 1]; } public boolean isEmpty() { return size == 0; } public boolean isFull() { return size == data.length; } }
Python β€” shifting array
class MyCircularQueue: def __init__(self, k: int): self.data = [0] * k self.size = 0 def enQueue(self, value: int) -> bool: if self.isFull(): return False self.data[self.size] = value self.size += 1 return True def deQueue(self) -> bool: if self.isEmpty(): return False for i in range(1, self.size): self.data[i - 1] = self.data[i] self.size -= 1 return True def Front(self) -> int: return -1 if self.isEmpty() else self.data[0] def Rear(self) -> int: return -1 if self.isEmpty() else self.data[self.size - 1] def isEmpty(self) -> bool: return self.size == 0 def isFull(self) -> bool: return self.size == len(self.data)
MetricValue
TimeO(n) for deQueue, O(1) for the other methods
SpaceO(k)
03
Section Three Β· Optimal

Ring Buffer with Head + Size β€” O(1)

Keep the array fixed. Instead of moving elements, track where the logical front starts and how many items are currently stored. The next enqueue position is (head + size) % capacity. The rear element lives at (head + size - 1 + capacity) % capacity.

πŸ’‘ Mental model: Picture numbered seats around a merry-go-round. Riders do not physically slide when the front rider gets off. You simply move the β€œfront” sticker to the next seat. When a new rider boards, they take the seat immediately after the current rear, wrapping around to seat 0 when needed.
Algorithm β€” constant-time wraparound
  • State: store data[], head, size, and capacity.
  • enQueue(value): if full, return false; otherwise write to (head + size) % capacity and increment size.
  • deQueue(): if empty, return false; otherwise move head = (head + 1) % capacity and decrement size.
  • Front(): return data[head] when not empty.
  • Rear(): compute the logical last index with modulo normalization.
🎯 When to recognize this pattern:
  • Fixed capacity plus repeated front deletions is the giveaway.
  • If an interface says β€œbounded queue,” β€œwrap around,” or β€œreuse freed slots,” you want a ring buffer instead of shifting elements or allocating linked nodes.
Why keep size?:
  • Without extra state, head == tail is ambiguous β€” it can mean either empty or full.
  • Tracking size removes that ambiguity cleanly and keeps every method O(1).
04
Section Four Β· Walkthrough

Visual Walkthrough

Circular Queue β€” head stays logical, writes wrap physically
Example: k = 3, operations = enQueue(1), enQueue(2), enQueue(3), deQueue(), enQueue(4) Step 1 [1, -, -] head=0 size=1 rear=0 Step 2 [1, 2, -] head=0 size=2 rear=1 Step 3 [1, 2, 3] head=0 size=3 rear=2 full βœ“ Step 4 deQueue() β†’ logical queue becomes [2, 3] Physical array still [1, 2, 3], but head=1, size=2 Step 5 enQueue(4) writes to (head + size) % 3 = (1 + 2) % 3 = 0 Physical array becomes [4, 2, 3] Logical order is [2, 3, 4], so Rear() = 4 4 wrapped write 2 head 3 middle Physical array: [4, 2, 3] Logical queue: [2, 3, 4]
05
Section Five Β· Code

Code β€” Java & Python

Java β€” ring buffer
class MyCircularQueue { private final int[] data; private final int capacity; private int head = 0; private int size = 0; public MyCircularQueue(int k) { data = new int[k]; capacity = k; } public boolean enQueue(int value) { if (isFull()) return false; int tail = (head + size) % capacity; data[tail] = value; size++; return true; } public boolean deQueue() { if (isEmpty()) return false; head = (head + 1) % capacity; size--; return true; } public int Front() { return isEmpty() ? -1 : data[head]; } public int Rear() { if (isEmpty()) return -1; int tail = (head + size - 1 + capacity) % capacity; return data[tail]; } public boolean isEmpty() { return size == 0; } public boolean isFull() { return size == capacity; } }
Python β€” ring buffer
class MyCircularQueue: def __init__(self, k: int): self.data = [0] * k self.capacity = k self.head = 0 self.size = 0 def enQueue(self, value: int) -> bool: if self.isFull(): return False tail = (self.head + self.size) % self.capacity self.data[tail] = value self.size += 1 return True def deQueue(self) -> bool: if self.isEmpty(): return False self.head = (self.head + 1) % self.capacity self.size -= 1 return True def Front(self) -> int: return -1 if self.isEmpty() else self.data[self.head] def Rear(self) -> int: if self.isEmpty(): return -1 tail = (self.head + self.size - 1) % self.capacity return self.data[tail] def isEmpty(self) -> bool: return self.size == 0 def isFull(self) -> bool: return self.size == self.capacity
06
Section Six Β· Analysis

Complexity Analysis

ApproachTimeSpaceTrade-off
Shifting array O(n) deQueue O(k) Easy state management, but every front removal moves data.
Linked list + capacity O(1) O(k) Also works, but allocates one node per element and misses the ring-buffer lesson.
Ring buffer ← optimal O(1) all methods O(k) No shifts, no extra nodes, and wraparound is handled with modulo arithmetic.
Why not keep a moving tail pointer only?:
  • You can, but then you still need either a size field or one permanently unused slot to separate full from empty.
  • The head + size formulation is compact and less bug-prone in interviews.
07
Section Seven Β· Edge Cases

Edge Cases & Pitfalls

CaseExpected behaviorWhy it matters
k = 1The only slot alternates between full and emptySmall capacities expose off-by-one mistakes quickly.
Queue is emptydeQueue() returns false, Front()/Rear() return -1The interface uses sentinel values instead of exceptions.
Queue is fullenQueue() returns falseYou must reject writes instead of overwriting existing data.
Wraparound after deletionsFreed slots at the front are reusedThis is the entire advantage over a naive array queue.
Repeated cycles of fill β†’ empty β†’ fillAll operations stay O(1)Persistent correctness matters more than one successful example.
⚠ Common Mistake: Using head == tail to mean β€œempty” without any extra state breaks as soon as the queue becomes full, because a full ring can also make the indices equal. Track size explicitly, or intentionally waste one slot and code the full condition differently.

← Back to Queue problems