Circular Queues Explained

    Atul Kabra4 min readUpdated

    A circular queue is a fixed-size queue that treats its underlying array as a ring: when the rear reaches the end of the array, it wraps back around to the front to reuse slots that earlier dequeues freed up. This solves the wasted-space problem of a simple array queue while keeping enqueue and dequeue at O(1).

    The problem it solves

    In a plain array queue, front and rear only move forward. After several dequeues, the slots at the start of the array sit empty and unusable — the queue reports "full" even though there is free space at the beginning. A circular queue fixes this by letting the indices wrap around using the modulo operator, so freed slots get reused.

    How wraparound works

    Instead of rear = rear + 1, a circular queue does rear = (rear + 1) % capacity. When rear is at the last index, (rear + 1) % capacity brings it back to 0. The array behaves like a circle with no real "end". The same trick applies to front.

    The tricky part is telling full from empty, because in both cases front and rear can point to the same slot. A common, clear solution is to track a separate count of stored items.

    Implementing a circular queue in C++

    #include <iostream>
    using namespace std;
    
    class CircularQueue {
        int* data;
        int capacity;
        int front;  // index of the front element
        int rear;   // index of the last element
        int count;  // how many elements are stored
    
    public:
        CircularQueue(int cap) {
            capacity = cap;
            data = new int[cap];
            front = 0;
            rear = -1;
            count = 0;
        }
    
        ~CircularQueue() { delete[] data; }
    
        bool isFull()  { return count == capacity; }
        bool isEmpty() { return count == 0; }
    
        // Add a value at the rear, wrapping around if needed. O(1).
        bool enqueue(int value) {
            if (isFull()) return false;          // no room
            rear = (rear + 1) % capacity;        // wrap the rear index
            data[rear] = value;
            count++;
            return true;
        }
    
        // Remove and return the front value, wrapping around. O(1).
        bool dequeue(int& out) {
            if (isEmpty()) return false;         // nothing to remove
            out = data[front];
            front = (front + 1) % capacity;      // wrap the front index
            count--;
            return true;
        }
    };
    
    int main() {
        CircularQueue q(3);
        q.enqueue(10);
        q.enqueue(20);
        q.enqueue(30);   // queue is now full
    
        int x;
        q.dequeue(x);    // x = 10, frees a slot at the start
        q.enqueue(40);   // 40 wraps into the freed slot
    
        while (q.dequeue(x)) {
            cout << x << " ";  // prints: 20 30 40
        }
        return 0;
    }
    

    Notice how 40 reuses the slot that 10 vacated. Without wraparound, that slot would be stranded.

    Want to learn this properly?

    Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.

    Browse courses

    Where circular queues are used

    This pattern is everywhere in systems programming under the name ring buffer or circular buffer: audio and video streaming buffers, keyboard input buffers, network packet queues, and producer-consumer pipelines. Any time you have a fixed amount of memory and a steady flow of data passing through, a circular queue is the natural fit.

    Common mistakes

    • Confusing full and empty. When front and rear collide, you cannot tell full from empty by indices alone. Track a count (as above) or sacrifice one slot to disambiguate.
    • Forgetting the modulo. Incrementing rear without % capacity runs off the end of the array. The wraparound is the whole point.
    • Off-by-one on capacity. If you use the "leave one slot empty" trick instead of a count, remember your real capacity is size - 1.
    • Mixing the two strategies. Either use a count or leave a sentinel slot — do not half-implement both, or your full/empty checks will disagree.
    • Resizing assumptions. A circular queue is fixed-size by design. If you need it to grow, you must allocate a bigger array and copy elements in order, which is more involved than a normal array because of the wrap.

    FAQ

    Is a circular queue the same as a ring buffer? In practice, yes — ring buffer and circular buffer are common names for the same idea, especially in systems and embedded programming.

    Why not just use a normal queue? A linked queue grows freely but uses more memory per item and is less cache-friendly. A circular queue gives you O(1) operations in a fixed, contiguous block, which is ideal when memory is bounded.

    Keep learning

    Start with the basics in Queues Explained, revisit Arrays as a Data Structure to understand the contiguous storage underneath, and browse the full DSA hub.

    Want to implement ring buffers with instructor feedback in Jalgaon? Join the waitlist for our Data Structures & Algorithms course.

    Want to learn this properly?

    Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.

    Browse courses
    Atul Kabra

    Founder, Infoplanet

    Atul Kabra founded Infoplanet in 2001 and has spent over two decades teaching programming — C, C++, Java, databases and more — to students across Maharashtra.

    Related guides