The problem
Need efficient fixed-size buffer for streaming data or logging
The solution
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;
}
}Parameters
capacitynumberMaximum capacity of the buffer
Put it to work
// 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');