JavaScriptmediumJavaScript

Circular Buffer / Ring Buffer

Fixed-size circular buffer for efficient data streaming

01

The problem

Need efficient fixed-size buffer for streaming data or logging

02

The solution

JavaScript
class CircularBuffer {
  constructor(capacity) {
    this.capacity = capacity;
    this.buffer = new Array(capacity);
    this.head = 0;
    this.tail = 0;
    this.size = 0;
    this.overwrite = false;
  }
  
  // Enable/disable overwrite when full
  setOverwrite(enabled) {
    this.overwrite = enabled;
    return this;
  }
  
  // Add item to buffer
  push(item) {
    if (this.isFull()) {
      if (!this.overwrite) {
        throw new Error('Buffer is full');
      }
      // Overwrite oldest item
      this.head = (this.head + 1) % this.capacity;
      this.size--;
    }
    
    this.buffer[this.tail] = item;
    this.tail = (this.tail + 1) % this.capacity;
    this.size++;
    return this;
  }
  
  // Remove and return oldest item
  pop() {
    if (this.isEmpty()) {
      throw new Error('Buffer is empty');
    }
    
    const item = this.buffer[this.head];
    this.buffer[this.head] = undefined;
    this.head = (this.head + 1) % this.capacity;
    this.size--;
    return item;
  }
  
  // Peek at oldest item without removing
  peek() {
    if (this.isEmpty()) {
      throw new Error('Buffer is empty');
    }
    return this.buffer[this.head];
  }
  
  // Get item at specific index (0 = oldest)
  get(index) {
    if (index < 0 || index >= this.size) {
      throw new Error('Index out of bounds');
    }
    
    const actualIndex = (this.head + index) % this.capacity;
    return this.buffer[actualIndex];
  }
  
  // Get all items as array
  toArray() {
    const result = [];
    for (let i = 0; i < this.size; i++) {
      result.push(this.get(i));
    }
    return result;
  }
  
  // Clear buffer
  clear() {
    this.buffer = new Array(this.capacity);
    this.head = 0;
    this.tail = 0;
    this.size = 0;
    return this;
  }
  
  // Check if buffer is empty
  isEmpty() {
    return this.size === 0;
  }
  
  // Check if buffer is full
  isFull() {
    return this.size === this.capacity;
  }
  
  // Get current size
  getSize() {
    return this.size;
  }
  
  // Get capacity
  getCapacity() {
    return this.capacity;
  }
  
  // Find item by predicate
  find(predicate) {
    for (let i = 0; i < this.size; i++) {
      const item = this.get(i);
      if (predicate(item, i, this)) {
        return item;
      }
    }
    return undefined;
  }
  
  // Find index by predicate
  findIndex(predicate) {
    for (let i = 0; i < this.size; i++) {
      const item = this.get(i);
      if (predicate(item, i, this)) {
        return i;
      }
    }
    return -1;
  }
  
  // Iterate over items
  forEach(callback) {
    for (let i = 0; i < this.size; i++) {
      callback(this.get(i), i, this);
    }
    return this;
  }
  
  // Map buffer to array
  map(callback) {
    const result = [];
    this.forEach((item, index) => {
      result.push(callback(item, index, this));
    });
    return result;
  }
  
  // Filter buffer
  filter(predicate) {
    const result = [];
    this.forEach((item, index) => {
      if (predicate(item, index, this)) {
        result.push(item);
      }
    });
    return result;
  }
}

03

Parameters

capacitynumber

Maximum capacity of the buffer

04

Put it to work

Example
// Create a circular buffer with capacity 5
const buffer = new CircularBuffer(5);

// Add items
buffer.push('A').push('B').push('C').push('D').push('E');
console.log(buffer.toArray()); // ['A', 'B', 'C', 'D', 'E']

// Buffer is now full
console.log(buffer.isFull()); // true

// Enable overwrite
buffer.setOverwrite(true);

// Add more items - overwrites oldest
buffer.push('F');
console.log(buffer.toArray()); // ['B', 'C', 'D', 'E', 'F']

// Get specific item
console.log(buffer.get(0)); // 'B' (oldest)
console.log(buffer.get(4)); // 'F' (newest)

// Remove oldest item
console.log(buffer.pop()); // 'B'
console.log(buffer.toArray()); // ['C', 'D', 'E', 'F']

// Use as logging buffer
const logBuffer = new CircularBuffer(1000);
function logMessage(level, message) {
  logBuffer.push({
    timestamp: new Date(),
    level,
    message
  });
}

// Process recent logs
const recentErrors = logBuffer.filter(log => log.level === 'error');