Skip to content

Circular Queue

A fixed-size queue where the front and rear pointers wrap around — Like a rotary sushi bar


Concept

Real-Life Analogy

** Rotary sushi bar**: Plates move around on a conveyor belt. When a plate reaches the end, it loops back to the start. The belt has a fixed number of positions — once it's full, no more plates can be added until someone takes one.

More examples:

  • CPU task scheduling: Fixed-size task queue in an operating system
  • Data buffer: Audio/video streaming buffers use circular queues
  • Traffic light system: Fixed-cycle traffic control

Why Circular Queue?

A regular queue using an array wastes space: after dequeue, the front position becomes empty but can't be reused. A circular queue reuses those positions by wrapping around.

Regular Queue (wasteful):
  [ ][ ][C][D][E]  ← front positions A, B are wasted
   ↑        ↑
  front    rear

Circular Queue (efficient):
  [F][G][C][D][E]  ← F and G fill the freed spots
       ↑     ↑
     front rear

When to Use

  • Fixed-size buffer scenarios
  • Streaming data processing
  • Embedded systems with limited memory
  • When you know the maximum queue size in advance

Code

javascript
class CircularQueue {
  constructor(capacity = 5) {
    this.items = new Array(capacity)
    this.capacity = capacity
    this._front = -1
    this._rear = -1
    this.count = 0
  }

  // Add an element to the rear
  enqueue(element) {
    if (this.isFull()) return false

    if (this.isEmpty()) {
      this._front = 0
      this._rear = 0
    } else {
      this._rear = (this._rear + 1) % this.capacity
    }

    this.items[this._rear] = element
    this.count++
    return true
  }

  // Remove and return the front element
  dequeue() {
    if (this.isEmpty()) return undefined

    const element = this.items[this._front]

    if (this._front === this._rear) {
      // Last element — reset queue
      this._front = -1
      this._rear = -1
    } else {
      this._front = (this._front + 1) % this.capacity
    }

    this.count--
    return element
  }

  // View front element without removing
  front() {
    return this.isEmpty() ? undefined : this.items[this.front]
  }

  // View rear element without removing
  rear() {
    return this.isEmpty() ? undefined : this.items[this.rear]
  }

  isEmpty() {
    return this.count === 0
  }
  isFull() {
    return this.count === this.capacity
  }
  size() {
    return this.count
  }
}

Key points:

  • (index + 1) % capacity is the wrap-around trick — when index reaches the end, it jumps back to 0
  • Tracking count separately lets us distinguish empty vs full (both have front === rear)

Complexity

OperationTimeSpace
enqueueO(1)O(1)
dequeueO(1)O(1)
frontO(1)O(1)
rearO(1)O(1)
isEmptyO(1)O(1)
isFullO(1)O(1)

Space: O(n) where n is the capacity. Unlike a dynamic queue, the memory is fixed and pre-allocated.


Visualization

Size: 0Capacity: 7Front: -Rear: -

Interview Questions

1. Design a Circular Queue

LeetCode 622: Design Circular Queue

Implement a circular queue with enQueue, deQueue, Front, Rear, isEmpty, and isFull.

Online Playground
Output
Click ▶ Run to see results

Common Pitfalls

MistakeWhy it's wrong
Using front === rear to detect emptyAlso true when the queue is full — use count instead
Forgetting modulo on pointer incrementrear++ will go out of bounds after the last index
Not resetting pointers on last dequeueAfter removing the last element, front and rear both point to a stale index

Decision Guide

ScenarioUse Case
Fixed max sizeCircular Queue
Unknown max sizeUse a dynamic Queue instead
Need O(1) all operationsCircular Queue
Memory is constrainedCircular Queue (pre-allocated)

MIT Licensed | Made with ❤️ for JS learners