Circular Queue

A normal queue built on a plain array has one annoying problem. You keep removing items from the front. That front space just sits there empty. The array says it is full. But half of it is wasted. A circular queue fixes exactly this. So let us see how it reuses that empty space.

🎯 The Problem With a Simple Array Queue

Picture a queue made from an array of size 5. You add 5 people. Now it is full. Next you serve (remove) 2 people from the front. Those two slots at the start are empty now, right?

But here is the pain. In a simple queue, front moves forward and rear is stuck at the last index. When you try to add a new person, rear cannot move. It already points to the end of the array. So the queue reports that it is full even though two slots are clearly free.

That wasted space at the front is the whole problem. A circular queue is a queue where the rear wraps back to index 0 once it reaches the end. So the empty front slots get reused.

Think of it like a round dinner table. When you reach the last chair, the next chair is the first one again. There is no real “end” because it loops around.

Note

The array is still a straight line in memory. We only pretend it is a circle by doing clever index math. Nothing physically bends.

🔁 How Wrapping Works

The trick is one small formula. Instead of writing rear = rear + 1, we write rear = (rear + 1) % capacity.

The % is the modulo operator. It gives the remainder after division. So when rear is at the last index and we add 1, the remainder brings it back to 0.

Here is the same idea in plain points:

  • capacity is the total size of the array, say 5.
  • Normally rear goes 0, 1, 2, 3, 4.
  • After 4, (4 + 1) % 5 is 5 % 5 which is 0. So rear jumps back to the front.
  • The same % capacity trick is used when moving front forward too.

That single % capacity is what makes the queue circular. Let us picture it.

idx 0

idx 1

idx 2

idx 3

idx 4

See how index 4 points back to index 0? That arrow is the modulo formula in action.

🧮 Full and Empty: The Tricky Part

There is one catch with circular queues. How do you tell “completely empty” from “completely full”? In both cases front and rear can sit close together. So we keep a simple size counter. It tells us how many items are inside right now.

The two checks become easy:

  • The queue is empty when size is 0.
  • The queue is full when size equals capacity.

This counter approach is the cleanest way for beginners. No confusing index tricks needed.

📝 Steps to Build a Circular Queue

Before we jump into code, here is the plan. We keep an array, a front index, a rear index, and a size counter.

To enqueue (add an item at the rear):

  1. If size equals capacity, the queue is full. Stop and report it.
  2. Move rear forward with wrapping: rear = (rear + 1) % capacity.
  3. Put the new value at the rear position.
  4. Increase size by 1.

To dequeue (remove an item from the front):

  1. If size is 0, the queue is empty. Stop and report it.
  2. Read the value at the front position.
  3. Move front forward with wrapping: front = (front + 1) % capacity.
  4. Decrease size by 1.
  5. Return the value you read.

We start front at 0 and rear at -1 (or capacity - 1). So the very first enqueue lands rear at index 0.

💻 Circular Queue in 5 Languages

Now let us write it. The code below builds a small circular queue of capacity 5. It fills the queue. Then it removes a couple of items. Then it adds new ones so the rear wraps around. Finally it prints what is inside.

circular_queue.py
CAP = 5
queue = [0] * CAP
front = 0
rear = -1
size = 0
# Add an item at the rear, wrapping if needed
def enqueue(value):
global rear, size
if size == CAP:
print(f"Queue is full, cannot add {value}")
return
rear = (rear + 1) % CAP # wrap rear back to 0 at the end
queue[rear] = value
size += 1
print(f"Enqueued {value}")
# Remove an item from the front, wrapping if needed
def dequeue():
global front, size
if size == 0:
print("Queue is empty, nothing to remove")
return
value = queue[front]
front = (front + 1) % CAP # wrap front back to 0 at the end
size -= 1
print(f"Dequeued {value}")
# Print items from front to rear in order
def display():
items = [str(queue[(front + i) % CAP]) for i in range(size)]
print("Queue: " + " ".join(items))
enqueue(10)
enqueue(20)
enqueue(30)
enqueue(40)
enqueue(50)
dequeue() # removes 10
dequeue() # removes 20
enqueue(60) # rear wraps to index 0
enqueue(70) # rear wraps to index 1
display()

The output of the above code will be:

Output
Enqueued 10
Enqueued 20
Enqueued 30
Enqueued 40
Enqueued 50
Dequeued 10
Dequeued 20
Enqueued 60
Enqueued 70
Queue: 30 40 50 60 70

Look at the last line of output. After removing 10 and 20, those slots at index 0 and 1 became free. Then 60 and 70 wrapped around. They landed in exactly those reused slots. That is the circular queue at work.

Tip

Both enqueue and dequeue run in O(1) time. There is no shifting of elements like a simple array queue might do. We just touch one index and update the counter. That is the big win.

⏱️ Time and Space Complexity

Here is how the circular queue operations stack up.

Operation Time Space
Enqueue O(1) O(1)
Dequeue O(1) O(1)
Peek (front) O(1) O(1)
Whole queue storage O(n)

The whole queue needs O(n) space for n slots. Every single operation stays at O(1) because nothing ever shifts.

⚠️ Common Mistakes to Avoid

A few traps catch beginners with circular queues. Watch out for these:

  • Forgetting the % capacity and writing plain rear + 1. Then rear runs off the end of the array and crashes.
  • Not keeping a size counter and then mixing up empty with full. Both states can have front and rear in the same spot.
  • Forgetting to check “is it full?” before enqueue. You would overwrite a value that has not been served yet.
  • Starting rear at 0 instead of -1, so the first item lands in the wrong slot. Start rear at -1 with this counter style.

🧩 What You’ve Learned

✅ A simple array queue wastes the empty front slots and reports “full” too early.

✅ A circular queue reuses that space by wrapping the index with rear = (rear + 1) % capacity.

✅ A size counter is the clean way to tell an empty queue from a full one.

✅ Enqueue and dequeue both run in O(1) time because nothing ever shifts.

✅ Start front at 0 and rear at -1 so the first enqueue lands correctly.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What problem does a circular queue solve compared to a simple array queue?

    Why: A circular queue wraps the rear back to the start, so the freed front slots get reused instead of being wasted.

  2. 2

    Which formula correctly moves the rear index in a circular queue?

    Why: The modulo with capacity makes the index wrap back to 0 once it passes the last slot.

  3. 3

    Why do we keep a size counter in this circular queue?

    Why: Front and rear can sit in the same spot when empty or full, so the size counter clears up which state it is.

  4. 4

    What is the time complexity of enqueue and dequeue in a circular queue?

    Why: Both touch just one index and update the counter, so they run in constant O(1) time with no shifting.

🚀 What’s Next?

Now that the circular queue makes sense, keep building your queue skills:

Share & Connect