MediumChapter 9: Circular Queue (Java). Difficulty: Beginner → Intermediate… | by Pavan Pithdiya | Medium
Sitemap

Chapter 9: Circular Queue (Java)

8 min readAug 11, 2026

--

Difficulty: Beginner → Intermediate
Prerequisites: Queue Fundamentals, Queue Using Array, Arrays, Java Basics

Press enter or click to view image in full size

Learning Objectives

After completing this chapter, you will be able to:

  • Understand why a Circular Queue is needed.
  • Learn how a Circular Queue solves the memory wastage problem of a linear queue.
  • Understand the wrap-around concept using the modulo (%) operator.
  • Implement a Circular Queue from scratch in Java.
  • Handle overflow and underflow conditions.
  • Perform dry runs for all queue operations.
  • Analyze time and space complexity.
  • Answer common interview questions confidently.

Introduction

In the previous chapter, we implemented a Linear Queue using an Array.

Although enqueue and dequeue operations were O(1), the implementation had one major drawback:

The queue wastes memory after dequeue operations because freed positions at the beginning of the array cannot be reused.

A Circular Queue eliminates this problem by treating the array as if its first and last positions are connected in a circle.

Instead of stopping when rear reaches the last index, it wraps around to the beginning of the array if space is available.

Why Do We Need a Circular Queue?

Consider a queue of size 5.

Initially:

Index

0 1 2 3 4
10 20 30 _ _

Front = 0
Rear = 2

Now perform:

dequeue()
dequeue()

Queue becomes:

Index

0 1 2 3 4
10 20 30 _ _
↑
Front

Rear = 2

Now insert:

enqueue(40)
enqueue(50)

Memory:

Index

0 1 2 3 4
10 20 30 40 50

Front = 2
Rear = 4

The queue contains only:

30 40 50

However, trying:

enqueue(60)

produces:

Queue Overflow

Although indexes 0 and 1 are free.

How Circular Queue Solves This Problem

Instead of treating the array as a straight line, imagine it as a circle.

           Index 0
●
● ●
Index 4 Index 1
● ●
Index 3 Index 2

When the rear reaches the last index:

Rear = capacity - 1

the next insertion goes back to:

Rear = 0

This process is called wrap-around.

Circular Queue Visualization

Capacity = 5

Initially

          0
+-------+
4 / \ 1
| |
3 \ / 2
+-------+
Front = -1
Rear = -1

After inserting:

10
20
30
Index

0 1 2 3 4
10 20 30 _ _

Front = 0
Rear = 2

After removing:

10
20

Index

0 1 2 3 4
10 20 30 _ _

Front = 2
Rear = 2

Now insert:

40
50

Index

0 1 2 3 4
10 20 30 40 50

Front = 2
Rear = 4

Next insertion:

enqueue(60)

Instead of Overflow

Rear wraps around:

Index

0 1 2 3 4
60 20 30 40 50

Rear = 0
Front = 2

Logical Queue

Front
↓
30 → 40 → 50 → 60
↑
Rear

Notice:

Even though memory looks unordered, the logical order remains correct.

The Modulo (%) Operator

The entire Circular Queue works because of one simple formula:

rear = (rear + 1) % capacity;

Suppose:

capacity = 5
| Current Rear | Calculation | New Rear |
| ------------ | ----------- | -------- |
| 0 | (0+1)%5 | 1 |
| 1 | (1+1)%5 | 2 |
| 2 | (2+1)%5 | 3 |
| 3 | (3+1)%5 | 4 |
| 4 | (4+1)%5 | 0 ✅ |

This is called circular indexing.

Similarly,

front = (front + 1) % capacity;

moves the front pointer correctly after a dequeue.

Circular Queue States

Empty Queue

Front = -1
Rear = -1
Queue[]

Queue with One Element

Front
↓

10
↑
Rear

Wrapped Queue

Index

0 1 2 3 4
60 _ 30 40 50

Front = 2
Rear = 0

Logical Queue

30 → 40 → 50 → 60

Queue Operations

| Operation | Description             |
| --------- | ----------------------- |
| Enqueue | Insert at Rear |
| Dequeue | Remove from Front |
| Peek | View Front element |
| isEmpty | Check if Queue is empty |
| isFull | Check if Queue is full |
| Size | Number of elements |
| Display | Print Queue |

Enqueue Operation

Current Queue

Front
↓
30 → 40 → 50
↑
Rear

Insert

enqueue(60)

Algorithm

If Queue is Full

Overflow
Else If Queue is Empty
Front = Rear = 0
Else
Rear = (Rear + 1) % Capacity
Insert value

Java Implementation of Enqueue

public void enqueue(int value) {

if (isFull()) {
System.out.println("Queue Overflow");
return;
}
if (isEmpty()) {
front = rear = 0;
} else {
rear = (rear + 1) % capacity;
}
queue[rear] = value;
size++;
}

Dry Run of Enqueue

Capacity = 5

Initially

Front = -1
Rear = -1

enqueue(10)

Front = 0
Rear = 010

enqueue(20)

Rear = 1
10 20

enqueue(30)

Rear = 2
10 20 30

enqueue(40)

Rear = 3

enqueue(50)

Rear = 4

dequeue()

Front becomes

1

enqueue(60)

Get Pavan Pithdiya’s stories in your inbox

Join Medium for free to get updates from this writer.

Rear

(4+1)%5
=0

Queue becomes

60 20 30 40 50

Logical order

20 30 40 50 60

Dequeue Operation

Current Queue

20 30 40 50 60

Remove

20

Algorithm

If Queue Empty

Underflow
Else
value = queue[front]
front = (front + 1) % capacity

Java Implementation of Dequeue

public int dequeue() {

if (isEmpty()) {
System.out.println("Queue Underflow");
return -1;
}
int value = queue[front];
if (front == rear) {
front = rear = -1;
} else {
front = (front + 1) % capacity;
}
size--;
return value;
}

Peek Operation

public int peek() {

if (isEmpty()) {
System.out.println("Queue is Empty");
return -1;
}
return queue[front];
}

isEmpty()

public boolean isEmpty() {
return front == -1;
}

isFull()

A Circular Queue is full when moving rear one step ahead would make it equal to front.

public boolean isFull() {
return (rear + 1) % capacity == front;
}

Example

Capacity = 5

Front = 2
Rear = 1

(1+1)%5 = 2

Front = 2

Queue Full

Display Operation

public void display() {

if (isEmpty()) {
System.out.println("Queue is Empty");
return;
}
int i = front;
while (true) {
System.out.print(queue[i] + " ");
if (i == rear)
break;
i = (i + 1) % capacity;
}
System.out.println();
}

Complete Java Implementation

public class CircularQueue {

private int[] queue;
private int front;
private int rear;
private int size;
private int capacity;
public CircularQueue(int capacity) {
this.capacity = capacity;
queue = new int[capacity];
front = -1;
rear = -1;
size = 0;
}
public boolean isEmpty() {
return front == -1;
}
public boolean isFull() {
return (rear + 1) % capacity == front;
}
public void enqueue(int value) {
if (isFull()) {
System.out.println("Queue Overflow");
return;
}
if (isEmpty()) {
front = rear = 0;
} else {
rear = (rear + 1) % capacity;
}
queue[rear] = value;
size++;
}
public int dequeue() {
if (isEmpty()) {
System.out.println("Queue Underflow");
return -1;
}
int value = queue[front];
if (front == rear) {
front = rear = -1;
} else {
front = (front + 1) % capacity;
}
size--;
return value;
}
public int peek() {
if (isEmpty()) {
System.out.println("Queue is Empty");
return -1;
}
return queue[front];
}
public int size() {
return size;
}
public void display() {
if (isEmpty()) {
System.out.println("Queue is Empty");
return;
}
int i = front;
while (true) {
System.out.print(queue[i] + " ");
if (i == rear)
break;
i = (i + 1) % capacity;
}
System.out.println();
}
public static void main(String[] args) {
CircularQueue queue = new CircularQueue(5);
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
queue.enqueue(40);
queue.enqueue(50);
queue.display();
System.out.println("Removed: " + queue.dequeue());
System.out.println("Removed: " + queue.dequeue());
queue.enqueue(60);
queue.enqueue(70);
queue.display();
System.out.println("Front: " + queue.peek());
System.out.println("Size: " + queue.size());
}
}

Sample Output

10 20 30 40 50
Removed: 10
Removed: 20
30 40 50 60 70Front: 30
Size: 5

Dry Run

Initial

[]

enqueue(10)

10

enqueue(20)

10 20

enqueue(30)

10 20 30

dequeue()

Removed
10

Queue

20 30

enqueue(40)

20 30 40

enqueue(50)

20 30 40 50

enqueue(60)

Rear wraps to index 0

Logical Queue

20 30 40 50 60

Edge Cases

Queue Overflow

Queue

10 20 30 40 50

Trying

enqueue(60)

Output

Queue Overflow

Queue Underflow

Queue

[]

Calling

dequeue();

Output

Queue Underflow

Single Element Queue

Queue

10

After

dequeue()

Both pointers reset.

Front = -1
Rear = -1

Time Complexity

| Operation | Time Complexity |
| --------- | --------------- |
| Enqueue | O(1) |
| Dequeue | O(1) |
| Peek | O(1) |
| isEmpty | O(1) |
| isFull | O(1) |
| Size | O(1) |
| Search | O(n) |
| Display | O(n) |

Space Complexity

For n elements:

Space Complexity = O(n)

Linear Queue vs Circular Queue

| Feature                             | Linear Queue | Circular Queue |
| ----------------------------------- | ------------ | -------------- |
| Memory Reuse | ❌ No | ✅ Yes |
| Overflow Before Array Is Truly Full | Yes | No |
| Wrap-around | No | Yes |
| Efficient Space Utilization | No | Yes |
| Enqueue | O(1) | O(1) |
| Dequeue | O(1) | O(1) |

Advantages

  • Eliminates memory wastage found in linear queues.
  • Efficiently reuses freed array positions.
  • All primary operations run in O(1) time.
  • Ideal for fixed-size buffers and scheduling systems.
  • Simple wrap-around logic using the modulo operator.

Disadvantages

  • Fixed capacity unless resized manually.
  • Wrap-around logic is more complex than a linear queue.
  • Incorrect pointer updates can lead to subtle bugs.

Real-World Applications

  • CPU scheduling (Round Robin)
  • Circular buffers
  • Keyboard input buffers
  • Audio and video streaming
  • Network packet buffering
  • Embedded systems
  • Producer–Consumer problems

Common Interview Questions

  1. Why is a Circular Queue better than a Linear Queue?
  2. What role does the modulo (%) operator play?
  3. How do you detect if a Circular Queue is full?
  4. How do you distinguish between an empty queue and a full queue?
  5. What happens when rear reaches the last index?
  6. Why are enqueue() and dequeue() both O(1)?
  7. Where are Circular Queues used in real-world systems?

Common Mistakes

  • Forgetting to use the modulo operator while updating front or rear.
  • Incorrectly checking the full condition.
  • Not resetting front and rear when removing the last element.
  • Confusing the physical array layout with the logical queue order.

Best Practices

  • Maintain a separate size variable when possible—it simplifies state management.
  • Use the modulo operator consistently for pointer updates.
  • Reset the queue to the empty state (front = rear = -1) after removing the last element.
  • For production Java code, prefer ArrayDeque unless a fixed-capacity circular buffer is specifically required.

Chapter Summary

  • A Circular Queue is an optimized array-based queue that treats the array as a circle.
  • It solves the memory wastage problem of a linear queue by reusing freed positions through wrap-around.
  • The modulo (%) operator enables efficient circular indexing for both front and rear.
  • Core operations (enqueue, dequeue, peek) execute in O(1) time.
  • Circular Queues are widely used in operating systems, networking, embedded systems, and buffering applications where efficient use of fixed-size memory is essential.

Next Chapter

Chapter 10 — Queue Using Linked List (Java)

In the next chapter, you’ll learn:

  • Dynamic Queue implementation using a Linked List.
  • Node structure and memory layout.
  • Why linked-list queues don’t suffer from fixed-capacity limitations.
  • Complete Java implementation.
  • Detailed dry runs.
  • Complexity analysis.
  • Comparison with array-based and circular queues.
  • Interview questions and best practices.

--

--

Pavan Pithdiya
Pavan Pithdiya

Software Engineer by profession, explorer by mindset — navigating the digital world with curiosity that goes far beyond code.