Skip to content

Circular Queue Visualizer

1/1

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

  1. Keep an array A of capacity slots, front (index of the first element), rear (index of the last element, initially −1) and count, the number of elements.
  2. enqueue(x): if count == capacity the queue is full. Otherwise set rear = (rear + 1) % capacity, write A[rear] = x and increase count.
  3. dequeue(): if count == 0 the queue is empty. Otherwise read x = A[front], set front = (front + 1) % capacity, decrease count and return x.
  4. 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 is count == 0, full is count == capacity, and every slot is usable. This is the version in the code below.
  • Gap slot: let rear point at the next free slot and never fill the last free one. Empty is front == 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 front as well as rear — 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) % capacity in the gap design.
  • Testing front == rear for *empty* in the counter design — a queue with one element has front == rear too.
  • Stepping backwards with (i - 1) % capacity in 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

CaseTimeSpaceWhy
enqueueO(1)O(1)One modulo step and one write.
dequeueO(1)O(1)front steps forward modulo capacity; nothing shifts.
peek / isFull / isEmptyO(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 0

The 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

Last updated