CMD Guide
HomeDSAQueues

Diving Deeper – Circular Queues and Deques

A circular queue reuses freed array slots by treating the underlying array as a ring: front and rear pointers advance with modulo arithmetic (i+1) % capacity instead of stopping at the array's end, so a slot vacated by a dequeue at index 0 can be reused by an enqueue that wraps around from index n-1. A deque generalizes this ring further by allowing insertion and removal at both ends.

Recognize the pattern

Brute force → optimal

Brute force (plain array queue): keep a flat array; on every dequeue shift all remaining elements one slot left so index 0 is always the front. Enqueue is O(1) (append at the end), but each dequeue costs O(n) for the shift — n dequeues cost O(n²) total, and once rear hits the array end you falsely report overflow even with free space at the front.

Optimal (circular queue): track front, rear, and a size counter; advance both indices with modulo capacity. No element is ever shifted, so both operations are O(1) no matter how many times the pointers have wrapped.

Complexity, derived

Each enqueue performs a constant number of primitive steps: one full/empty check, one array write at arr[rear], one modulo-increment rear = (rear+1) % capacity, one increment of size. None of these steps depend on capacity or on how many elements are currently stored, so enqueue and dequeue are both O(1) time, worst case and amortized — contrast with the O(n) shift in the naive array queue. Deque operations (addFront, removeRear) add one modulo-decrement variant but are still O(1) each. Space is O(capacity) — one fixed array allocated up front, plus three O(1) integer pointers; no per-operation extra allocation, unlike a linked-list queue which pays a node allocation per enqueue.

Traced example: capacity 5

Start empty: front=0, rear=-1, size=0, array slots [_,_,_,_,_]. This code's convention is rear = index of the last occupied slot (not "next free slot"), which is why it starts at -1 rather than 0.

Opfrontrearsizearray (indices 0-4)
enqueue(10)001[10,_,_,_,_]
enqueue(20)012[10,20,_,_,_]
enqueue(30)023[10,20,30,_,_]
dequeue() → 10122[_,20,30,_,_]
enqueue(40)133[_,20,30,40,_]
enqueue(50)144[_,20,30,40,50]
enqueue(60)105[60,20,30,40,50]

The last enqueue computes rear = (4+1) % 5 = 0 and writes 60 into the slot vacated earlier by 10 — the wrap-around reuse that makes this a circular queue. After this step front=1 (the real front, holding 20 at index 1) and rear=0 (holding the most recently written 60 at index 0); both index 0 and index 1 hold real, occupied values — there is no free slot among them. The queue is now full (size == capacity == 5). Notice that (rear+1) % capacity = (0+1) % 5 = 1 = front: this is exactly the ambiguous condition for this convention (rear = last occupied slot). The same equation, (rear+1) % capacity == front, also held back at the very start (front=0, rear=-1: (-1+1)%5=0). Structurally the two states look identical from front/rear alone — only the separate size counter (0 vs. 5) tells them apart.

Java implementation

class CircularQueue {
    private final int[] arr;
    private int front = 0, rear = -1, size = 0;
    private final int capacity;

    CircularQueue(int capacity) {
        this.capacity = capacity;
        this.arr = new int[capacity];
    }

    boolean enqueue(int val) {
        if (size == capacity) return false; // overflow
        rear = (rear + 1) % capacity;
        arr[rear] = val;
        size++;
        return true;
    }

    int dequeue() {
        if (size == 0) throw new RuntimeException("underflow");
        int val = arr[front];
        front = (front + 1) % capacity;
        size--;
        return val;
    }

    int peekFront() {
        if (size == 0) throw new RuntimeException("empty");
        return arr[front];
    }

    boolean isEmpty() { return size == 0; }
    boolean isFull()  { return size == capacity; }
}

A deque adds mirror operations at the rear end using modulo subtraction (add capacity before the mod to avoid negative results in Java):

void addFront(int val) {
    if (size == capacity) throw new RuntimeException("overflow");
    front = (front - 1 + capacity) % capacity;
    arr[front] = val;
    size++;
}

int removeRear() {
    if (size == 0) throw new RuntimeException("underflow");
    int val = arr[rear];
    rear = (rear - 1 + capacity) % capacity;
    size--;
    return val;
}

Pitfalls

When to use / when not

Use a circular queue when capacity is bounded and known ahead of time and you need guaranteed O(1) enqueue/dequeue with zero per-operation allocation — ring buffers in networking stacks, audio/video streaming buffers, round-robin CPU schedulers. Use a deque when you need push/pop at both ends, e.g. implementing a sliding-window maximum or an LRU-adjacent structure.

Trade-off vs. a linked-list queue: a linked list needs no fixed capacity and never "overflows" (until memory runs out), but pays a heap allocation per node and worse cache locality; the circular array queue is faster and cache-friendly but must reject or resize on overflow. Trade-off vs. java.util.ArrayDeque: in production Java code, prefer ArrayDeque (it already implements circular-array doubling internally) — hand-rolling one is for interview/learning purposes or when a hard capacity cap is a functional requirement, not just an optimization.

Takeaways

Recall: In a circular queue of capacity 5 currently holding elements at indices 3 and 4 (front=3, rear=4, size=2), what are the new values of front, rear, and size after one dequeue followed by one enqueue?


Adapted and expanded from the original "Diving Deeper – Circular Queues and Deques" module, with added derivations, a traced example, an SVG diagram, and Java implementations for interview-prep depth.

🤖 Don't fully get this? Learn it with Claude

Stuck on Diving Deeper – Circular Queues and Deques? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Diving Deeper – Circular Queues and Deques** (DSA) and want to truly understand it. Explain Diving Deeper – Circular Queues and Deques from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Diving Deeper – Circular Queues and Deques** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Diving Deeper – Circular Queues and Deques** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Diving Deeper – Circular Queues and Deques** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes