What is a circular queue?
A circular queue (or ring buffer) is a fixed-size array queue whose indices wrap around: after the last slot comes slot 0, using (i + 1) % capacity. Slots freed by dequeue are reused, so enqueue and dequeue stay O(1) and freed slots are never stranded — the reason ring buffers sit inside keyboards, audio drivers and network cards.
How it works
- Keep an array
Aofcapacityslots,front(index of the first element),rear(index of the last element, initially −1) andcount, the number of elements. - enqueue(x): if
count == capacitythe queue is full. Otherwise setrear = (rear + 1) % capacity, writeA[rear] = xand increasecount. - dequeue(): if
count == 0the queue is empty. Otherwise readx = A[front], setfront = (front + 1) % capacity, decreasecountand returnx. - peek() returns
A[front]. Both indices only ever step forwards modulo the capacity, so no element ever moves.
Why wrap around?
In a plain array queue front and rear only move right, so the slots before front are lost and the queue can report *full* while it is half empty. A circular queue treats the array as a ring: when rear reaches the last index, the next enqueue writes to index 0 if that slot is free.
The modulo operator does the wrapping — (5 + 1) % 6 = 0 — so the code needs no special case for the end of the array. Think of the indices as the hands of a clock: they keep turning forwards, and the hour after 11 is 0 again.
Worked example: wrapping past the end
Capacity 6, holding 15, 42 and 8 at indices 2–4 (front = 2, rear = 4, count = 3):
enqueue(23): rear = (4 + 1) % 6 = 5,A[5] = 23, count 4.enqueue(4): rear = (5 + 1) % 6 = 0 — the index wraps to the start.A[0] = 4, count 5.enqueue(16): rear = 1,A[1] = 16, count 6 = capacity, so the queue is now full.dequeue(): returns 15 from index 2 and front becomes 3, count 5. Slot 2 is free and will be reused when rear comes round to it.
The queue order is always read from front forwards: 42, 8, 23, 4, 16 — even though 4 and 16 sit at lower indices than 42. The *Wrap-around demo* button in the visualizer plays a similar sequence on whatever is on screen.
Full or empty? Two ways to tell
With only front and rear, a full ring and an empty ring look identical, because in both cases the pointers meet. There are two classic fixes, and the visualizer's *Full check* switch animates both:
- Counter: keep
count. Empty iscount == 0, full iscount == capacity, and every slot is usable. This is the version in the code below. - Gap slot: let
rearpoint at the next free slot and never fill the last free one. Empty isfront == rear; full is(rear + 1) % capacity == front. You lose one slot but need no counter, which suits lock-free single-producer, single-consumer buffers.
A third design, used by the Linux kernel's kfifo and many high-performance queues, keeps ever-increasing head and tail counters and reduces them modulo a power-of-two capacity only when indexing; the size is simply tail - head.
Common mistakes
- Forgetting the modulo on
frontas well asrear— both pointers must wrap. - Computing the size as
rear - front + 1; after a wrap that can be negative. Use the counter, or(rear - front + capacity) % capacityin the gap design. - Testing
front == rearfor *empty* in the counter design — a queue with one element hasfront == reartoo. - Stepping backwards with
(i - 1) % capacityin C, C++ or Java, where%can return a negative number. Write(i - 1 + capacity) % capacity. - Reading the array in index order: 4, 16, …, 42, 8, 23 is not the queue order.
Where ring buffers are used
- Keyboard and serial-port (UART) input buffers, and audio or video streaming buffers.
- Network interface cards and routers queueing packets.
- Keeping the most recent N log lines, an undo history with a size limit, or a moving average over a data stream.
- Producer–consumer pipelines between threads. Python's
collections.deque(maxlen=n)behaves like a ring that drops its oldest item.
In interviews, LeetCode 622 *Design Circular Queue* asks for exactly the counter version below, and 641 *Design Circular Deque* adds the other end. Be ready to explain why the modulo keeps everything O(1), why the gap design sacrifices a slot, and how to grow the buffer: allocate a larger array and copy the elements in queue order, starting from front.
Time and space complexity
| Case | Time | Space | Why |
|---|---|---|---|
| enqueue | O(1) | O(1) | One modulo step and one write. |
| dequeue | O(1) | O(1) | front steps forward modulo capacity; nothing shifts. |
| peek / isFull / isEmpty | O(1) | O(1) | A comparison against count (or the pointers). |
| storage | — | O(capacity) | A fixed array; the gap design leaves one slot unused. |
Circular Queue in Python
class CircularQueue:
"""Ring buffer: indices wrap with (i + 1) % capacity; a counter tells full from empty."""
def __init__(self, capacity):
self.A = [None] * capacity
self.capacity = capacity
self.front = 0 # first element
self.rear = -1 # last element
self.count = 0
def enqueue(self, x):
if self.count == self.capacity:
raise IndexError("Queue full")
self.rear = (self.rear + 1) % self.capacity # wraps to 0 after the last index
self.A[self.rear] = x
self.count = self.count + 1
def dequeue(self):
if self.count == 0:
raise IndexError("Queue empty")
x = self.A[self.front]
self.front = (self.front + 1) % self.capacity
self.count = self.count - 1
return x
def peek(self):
if self.count == 0:
raise IndexError("Queue empty")
return self.A[self.front]
def clear(self):
self.front = 0
self.rear = -1
self.count = 0
def is_empty(self):
return self.count == 0
def is_full(self):
return self.count == self.capacity
# q = CircularQueue(3); q.enqueue(1); q.enqueue(2); q.enqueue(3)
# q.dequeue() -> 1, then q.enqueue(4) reuses slot 0The visualizer above shows the same code in Python, C++, Java and JavaScript.
Frequently asked questions
What is a circular queue?
A queue stored in a fixed-size array whose front and rear indices wrap around to 0 after the last slot. It reuses the space freed by dequeues, so it never wastes slots.
How do you check if a circular queue is full?
With a counter, it is full when count == capacity. Without one, where rear is the next free slot, it is full when (rear + 1) % capacity == front.
Why does a circular queue keep one slot empty?
Only in the design without a counter: if every slot were filled, front == rear would mean both full and empty. Leaving one slot empty keeps the two states distinguishable.
What is the advantage of a circular queue over a linear queue?
It reuses the slots that dequeues free up, so it only reports full when it truly is full — with O(1) enqueue and dequeue and no shifting of elements.
How do you find the number of elements in a circular queue?
Keep a count variable, or compute (rear - front + capacity) % capacity when rear points at the next free slot.
Is a ring buffer the same as a circular queue?
In practice, yes: both are a fixed-size array used as a FIFO with wrap-around indices. "Ring buffer" or "circular buffer" is the more common name in systems programming.
Keep learning
- Queue VisualizerInteractive queue visualizer: watch enqueue and dequeue move the front and rear of an array queue, and see the wasted-space problem that circular queues fix.
- Deque VisualizerDeque visualizer: push and pop at both the front and back of a double-ended queue on a circular array, each in O(1), and see how it acts as a stack or a queue.
- Stack & Queue VisualizerStack and queue visualizer: animate push, pop, enqueue and dequeue, compare LIFO with FIFO, and step through circular queues, deques and stack applications.
- Stack VisualizerInteractive stack visualizer: watch push, pop and peek move the top pointer of an array stack, see overflow and underflow happen, and learn why each is O(1).
Last updated