Fixed-Size Circular Buffer

Store the most recent items in a fixed-capacity circular buffer with overwrite eviction, indexed access, iteration, and clearing.

TypescriptCli Tooling

Code

snippet
export class CircularBuffer<T> {
  private readonly items: Array<T | undefined>;
  private head = 0;
  private count = 0;

  public constructor(public readonly capacity: number) {
    if (!Number.isInteger(capacity) || capacity < 1) {
      throw new RangeError("capacity must be a positive integer");
    }
    this.items = new Array<T | undefined>(capacity);
  }

  public get size(): number {
    return this.count;
  }

  public get full(): boolean {
    return this.count === this.capacity;
  }

  public push(value: T): T | undefined {
    const evicted =
      this.count === this.capacity ? this.items[this.head] : undefined;
    this.items[this.head] = value;
    this.head = (this.head + 1) % this.capacity;
    if (this.count < this.capacity) this.count += 1;
    return evicted;
  }

  public at(index: number): T {
    if (!Number.isInteger(index) || index < 0 || index >= this.count) {
      throw new RangeError(`index out of range: ${index}`);
    }
    const start =
      (this.head - this.count + this.capacity) % this.capacity;
    return this.items[(start + index) % this.capacity] as T;
  }

  public toArray(): T[] {
    const output: T[] = [];
    for (let index = 0; index < this.count; index += 1) {
      output.push(this.at(index));
    }
    return output;
  }

  public clear(): void {
    this.items.fill(undefined);
    this.head = 0;
    this.count = 0;
  }

  public *[Symbol.iterator](): IterableIterator<T> {
    for (let index = 0; index < this.count; index += 1) {
      yield this.at(index);
    }
  }
}

const buffer = new CircularBuffer<number>(3);
console.log(buffer.push(1), buffer.push(2), buffer.push(3));
console.log(buffer.push(4));
console.log(buffer.toArray(), buffer.at(0), buffer.at(2), buffer.full);
console.log([...buffer]);

buffer.clear();
console.log(buffer.size, buffer.toArray());
try {
  buffer.at(0);
} catch (error) {
  console.log(error instanceof Error ? error.message : String(error));
}