Skip to content

Circular Array

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

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 AA of capacity nn 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 nn
  • a head index
  • a tail index

Positions wrap using modulo arithmetic:

index=(base+offset)modn 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 55.

stepoperationarrayheadtail
1push 8[8, -, -, -, -]01
2push 3[8, 3, -, -, -]02
3pop[8, 3, -, -, -]12
4push 5[8, 3, 5, -, -]13
5push 1[8, 3, 5, 1, -]14
6push 9[8, 3, 5, 1, 9]10

The tail wraps from index 44 back to 00.

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

operationtime
push_backO(1)O(1)
push_frontO(1)O(1)
pop_frontO(1)O(1)
pop_backO(1)O(1)

Space usage:

O(n) 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
}