title: Design Circular Queue slug: design-circular-queue difficulty: medium leetcode_id: 622 leetcode_url: https://leetcode.com/problems/design-circular-queue/ categories: - arrays - queue patterns: - two-pointers description: | Design your implementation of the circular queue. The circular queue is a linear data structure in which the operations are performed based on FIFO (First In First Out) principle, and the last position is connected back to the first position to make a circle. It is also called "Ring Buffer". One of the benefits of the circular queue is that we can make use of the spaces in front of the queue. In a normal queue, once the queue becomes full, we cannot insert the next element even if there is a space in front of the queue. But using the circular queue, we can use the space to store new values. Implement the `MyCircularQueue` class: - `MyCircularQueue(k)` Initializes the object with the size of the queue to be `k`. - `int Front()` Gets the front item from the queue. If the queue is empty, return `-1`. - `int Rear()` Gets the last item from the queue. If the queue is empty, return `-1`. - `boolean enQueue(int value)` Inserts an element into the circular queue. Return `true` if the operation is successful. - `boolean deQueue()` Deletes an element from the circular queue. Return `true` if the operation is successful. - `boolean isEmpty()` Checks whether the circular queue is empty or not. - `boolean isFull()` Checks whether the circular queue is full or not. You must solve the problem without using the built-in queue data structure in your programming language. constraints: | - `1 <= k <= 1000` - `0 <= value <= 1000` - At most `3000` calls will be made to `enQueue`, `deQueue`, `Front`, `Rear`, `isEmpty`, and `isFull`. examples: - input: | ["MyCircularQueue", "enQueue", "enQueue", "enQueue", "enQueue", "Rear", "isFull", "deQueue", "enQueue", "Rear"] [[3], [1], [2], [3], [4], [], [], [], [4], []] output: "[null, true, true, true, false, 3, true, true, true, 4]" explanation: | MyCircularQueue myCircularQueue = new MyCircularQueue(3); myCircularQueue.enQueue(1); // return True myCircularQueue.enQueue(2); // return True myCircularQueue.enQueue(3); // return True, queue is now [1, 2, 3] myCircularQueue.enQueue(4); // return False, queue is full myCircularQueue.Rear(); // return 3 myCircularQueue.isFull(); // return True myCircularQueue.deQueue(); // return True, removes 1, queue is [2, 3] myCircularQueue.enQueue(4); // return True, queue is now [2, 3, 4] myCircularQueue.Rear(); // return 4 explanation: intuition: | Imagine a circular track where runners line up at different positions. When the track is full and someone at the front leaves, a new runner can take that vacated spot — the track "wraps around" from the end back to the beginning. A circular queue works the same way. Unlike a standard array-based queue where you might shift all elements when dequeuing (expensive!) or waste space at the front after dequeuing, a circular queue uses **modular arithmetic** to wrap indices around. When the `rear` pointer reaches the end of the array, it wraps back to index `0` if there's space available. The key insight is that we need to track two things: 1. **Where the front of the queue is** (for dequeue and peek operations) 2. **How many elements are currently in the queue** (to know if it's empty or full) With these two pieces of information, we can compute where the rear is, and we can use the modulo operator (`%`) to wrap indices around the fixed-size array. approach: | We implement the circular queue using a **fixed-size array** with two pointers and a count: **Step 1: Initialise the data structure** - `data`: A fixed-size array of length `k` to store elements - `head`: Points to the front element (where we dequeue from) - `count`: Tracks the current number of elements in the queue   **Step 2: Implement enQueue (insert at rear)** - If the queue is full, return `false` - Calculate the rear index: `(head + count) % k` - Place the new element at this position - Increment `count` - Return `true`   **Step 3: Implement deQueue (remove from front)** - If the queue is empty, return `false` - Move `head` forward: `head = (head + 1) % k` - Decrement `count` - Return `true`   **Step 4: Implement Front and Rear** - `Front()`: Return `data[head]` if not empty, else `-1` - `Rear()`: Calculate rear index as `(head + count - 1) % k`, return that element if not empty   **Step 5: Implement isEmpty and isFull** - `isEmpty()`: Return `count == 0` - `isFull()`: Return `count == k`   The modulo operation is the magic that makes the "circular" behaviour work — it ensures indices wrap around when they exceed the array bounds. common_pitfalls: - title: Confusing Head and Tail Management description: | A common mistake is maintaining both `head` and `tail` pointers and struggling to differentiate between empty and full states. With two pointers, both `head == tail` could mean empty OR full! Using a `count` variable instead of a `tail` pointer eliminates this ambiguity entirely. You always know the state: `count == 0` means empty, `count == k` means full. wrong_approach: "Using head and tail pointers without a count" correct_approach: "Use head pointer + count, compute tail when needed" - title: Off-by-One Errors in Rear Calculation description: | When computing the rear index, remember that `rear` points to the *last element*, not the next empty slot. The formula is: `rear = (head + count - 1) % k` Not `(head + count) % k`, which would be the next insertion point. For example, if `head = 0`, `count = 3`, and `k = 5`, the rear is at index `2`, not `3`. wrong_approach: "rear = (head + count) % k" correct_approach: "rear = (head + count - 1) % k" - title: Forgetting to Handle Empty Queue Edge Cases description: | When the queue is empty, `Front()` and `Rear()` should return `-1`, not cause an index error or return garbage. Always check `isEmpty()` before accessing elements: ```python def Front(self): if self.isEmpty(): return -1 return self.data[self.head] ``` wrong_approach: "Directly accessing data[head] without checking empty" correct_approach: "Always check isEmpty() before accessing elements" key_takeaways: - "**Ring buffer pattern**: Circular queues are foundational for streaming data, bounded buffers, and producer-consumer scenarios" - "**Modular arithmetic**: The `%` operator is the key to wrapping indices around a fixed-size array" - "**Count vs. two pointers**: Tracking `count` separately simplifies empty/full detection and avoids ambiguous states" - "**O(1) operations**: All operations are constant time, making this ideal for high-throughput scenarios" time_complexity: "O(1). All operations (`enQueue`, `deQueue`, `Front`, `Rear`, `isEmpty`, `isFull`) execute in constant time." space_complexity: "O(k). We use a fixed-size array of length `k` to store up to `k` elements." solutions: - approach_name: Array with Head and Count is_optimal: true code: | class MyCircularQueue: def __init__(self, k: int): # Fixed-size array to store elements self.data = [0] * k # Index of the front element self.head = 0 # Current number of elements self.count = 0 # Maximum capacity self.capacity = k def enQueue(self, value: int) -> bool: # Cannot insert if queue is full if self.isFull(): return False # Calculate rear position using modular arithmetic # This wraps around when we reach the end of the array rear = (self.head + self.count) % self.capacity self.data[rear] = value self.count += 1 return True def deQueue(self) -> bool: # Cannot remove if queue is empty if self.isEmpty(): return False # Move head forward (with wrap-around) self.head = (self.head + 1) % self.capacity self.count -= 1 return True def Front(self) -> int: # Return -1 for empty queue if self.isEmpty(): return -1 return self.data[self.head] def Rear(self) -> int: # Return -1 for empty queue if self.isEmpty(): return -1 # Rear is at (head + count - 1), wrapped around rear = (self.head + self.count - 1) % self.capacity return self.data[rear] def isEmpty(self) -> bool: return self.count == 0 def isFull(self) -> bool: return self.count == self.capacity explanation: | **Time Complexity:** O(1) — All operations are constant time with no loops. **Space Complexity:** O(k) — Fixed array size determined at initialization. This implementation uses a single `head` pointer combined with a `count` variable. The rear position is computed on-demand using `(head + count) % capacity`. This approach is cleaner than maintaining separate head and tail pointers because the `count` variable unambiguously indicates whether the queue is empty or full. - approach_name: Linked List Implementation is_optimal: false code: | class ListNode: def __init__(self, val: int): self.val = val self.next = None class MyCircularQueue: def __init__(self, k: int): self.capacity = k self.count = 0 # Head and tail pointers for the linked list self.head = None self.tail = None def enQueue(self, value: int) -> bool: if self.isFull(): return False new_node = ListNode(value) if self.isEmpty(): # First element: both head and tail point to it self.head = new_node self.tail = new_node else: # Append to tail self.tail.next = new_node self.tail = new_node self.count += 1 return True def deQueue(self) -> bool: if self.isEmpty(): return False # Move head to next node self.head = self.head.next self.count -= 1 # If queue becomes empty, reset tail too if self.isEmpty(): self.tail = None return True def Front(self) -> int: if self.isEmpty(): return -1 return self.head.val def Rear(self) -> int: if self.isEmpty(): return -1 return self.tail.val def isEmpty(self) -> bool: return self.count == 0 def isFull(self) -> bool: return self.count == self.capacity explanation: | **Time Complexity:** O(1) — All operations are constant time. **Space Complexity:** O(k) — At most `k` nodes in the linked list. This alternative uses a singly linked list instead of an array. While it also achieves O(1) operations, it has higher memory overhead due to node objects and pointers. The array-based approach is generally preferred for its better cache locality and lower memory footprint. However, the linked list approach demonstrates that the "circular" concept is about the logical structure, not necessarily a physical ring in memory.