Circular Array

Array that wraps indices modulo capacity to support efficient cyclic access.

Circular Array

A circular array treats a fixed-size array as if it wraps around. Indices advance modulo the capacity, so the structure forms a logical ring.

You use it when you need constant-time insertions and removals at both ends within a bounded buffer.

Problem

Maintain a sequence $A$ of capacity $n$ that supports:

  • insert at front and back
  • remove from front and back
  • wrap indices without shifting elements

Structure

A circular array maintains:

  • an array of size $n$
  • a head index
  • a tail index

Positions wrap using modulo arithmetic:

$$ index = (base + offset) \bmod n $$

Algorithm

Insert at the back:

push_back(A, x):
    if size == capacity:
        error "full"

    A[tail] = x
    tail = (tail + 1) mod capacity
    size += 1

Remove from the front:

pop_front(A):
    if size == 0:
        error "empty"

    value = A[head]
    head = (head + 1) mod capacity
    size -= 1
    return value

Example

Let capacity be $5$.

step operation array head tail
1 push 8 [8, -, -, -, -] 0 1
2 push 3 [8, 3, -, -, -] 0 2
3 pop [8, 3, -, -, -] 1 2
4 push 5 [8, 3, 5, -, -] 1 3
5 push 1 [8, 3, 5, 1, -] 1 4
6 push 9 [8, 3, 5, 1, 9] 1 0

The tail wraps from index $4$ back to $0$.

Correctness

At all times:

  • elements occupy positions from head to tail in cyclic order
  • modulo arithmetic ensures indices remain within bounds
  • size tracks the number of valid elements

Thus operations maintain correct ordering without shifting elements.

Complexity

operation time
push_back $O(1)$
push_front $O(1)$
pop_front $O(1)$
pop_back $O(1)$

Space usage:

$$ O(n) $$

When to Use

Circular arrays are appropriate when:

  • a fixed-size buffer is sufficient
  • constant-time queue or deque operations are required
  • memory allocation must remain stable

They are less suitable when:

  • capacity must grow dynamically
  • random insertions in the middle are required

Implementation

class CircularArray:
    def __init__(self, capacity):
        self.a = [None] * capacity
        self.capacity = capacity
        self.head = 0
        self.tail = 0
        self.size = 0

    def push_back(self, x):
        if self.size == self.capacity:
            raise Exception("full")
        self.a[self.tail] = x
        self.tail = (self.tail + 1) % self.capacity
        self.size += 1

    def pop_front(self):
        if self.size == 0:
            raise Exception("empty")
        value = self.a[self.head]
        self.head = (self.head + 1) % self.capacity
        self.size -= 1
        return value
type CircularArray[T any] struct {
    data     []T
    head     int
    tail     int
    size     int
    capacity int
}

func NewCircularArray[T any](capacity int) *CircularArray[T] {
    return &CircularArray[T]{
        data:     make([]T, capacity),
        capacity: capacity,
    }
}

func (c *CircularArray[T]) PushBack(x T) bool {
    if c.size == c.capacity {
        return false
    }
    c.data[c.tail] = x
    c.tail = (c.tail + 1) % c.capacity
    c.size++
    return true
}

func (c *CircularArray[T]) PopFront() (T, bool) {
    var zero T
    if c.size == 0 {
        return zero, false
    }
    v := c.data[c.head]
    c.head = (c.head + 1) % c.capacity
    c.size--
    return v, true
}