High-performance circular buffer/queue for TypeScript and React - Perfect for logs, streaming data, and real-time updates
npm install circular-queue-react



High-performance circular buffer/queue for TypeScript and React.
Zero dependencies (React optional) â perfect for logs, streaming data, rolling windows, and real-time UI updates.
---
- ð CircularBuffer (Low-level) â direction-based circular buffer primitive (HEAD/TAIL)
- ðĶ BufferManager (High-level) â convenient API (push/pop single or arrays, peek helpers, utilities)
- âïļ React Hook â useCircularBuffer for automatic re-rendering in React
- ðŊ Type-Safe â full TypeScript generics support
- ⥠Fast â O(1) push/pop/peek operations
- ðŠķ Zero Dependencies â pure TypeScript implementation (React is optional)
- ð§ Flexible â logs, streaming feeds, caching, undo/redo, rolling averages, etc.
---
``bash`
npm install circular-queue-reactor
yarn add circular-queue-reactor
pnpm add circular-queue-react
> React Support: React 16.8+, 17, 18, 19 â
> _(React is required only if you use useCircularBuffer.)_
---
CircularBuffer is a minimal primitive:
- push(item, direction) â insertpop(direction)
- â remove 1get(direction, count?)
- â peek (non-destructive)iterable
- (oldest â newest)
`ts
import { CircularBuffer, Direction } from "circular-queue-react";
const buf = new CircularBuffer
// Push to TAIL (newest side)
buf.push("A", Direction.TAIL);
buf.push("B", Direction.TAIL);
// Push to HEAD (oldest side)
buf.push("Z", Direction.HEAD);
console.log(buf.get(Direction.HEAD)); // "Z" (oldest)
console.log(buf.get(Direction.TAIL)); // "B" (newest)
// Peek many
console.log(buf.get(Direction.HEAD, 2)); // ["Z","A"] (oldest -> newer)
console.log(buf.get(Direction.TAIL, 2)); // ["B","A"] (newest -> older)
// Iterate (oldest -> newest)
for (const x of buf) console.log(x);
// Pop
console.log(buf.pop(Direction.HEAD)); // removes oldest ("Z")
console.log(buf.pop(Direction.TAIL)); // removes newest ("B")
// Resize (logical capacity)
buf.resize(10);
// Clear
buf.clear();
`
BufferManager wraps CircularBuffer and provides a friendly API:
- pushHead / pushTail (single item or array)popHead
- / popTail (single or count)getHead
- / getTail (single or count)getAll
- utilities: , replaceAll, forEach/map/filter, getInfo, etc.
`ts
import { BufferManager } from "circular-queue-react";
const b = new BufferManager
// pushTail keeps newest at the end (TAIL)
b.pushTail(["A", "B", "C", "D"]);
console.log(b.getAll()); // ["B","C","D"] (keeps last 3)
// pushHead inserts at HEAD, preserves input order
b.pushHead(["X", "Y"]);
console.log(b.getAll()); // ["X","Y","B"] (oldest -> newest)
// Peek
console.log(b.getHead()); // "X"
console.log(b.getTail()); // "B"
console.log(b.getHead(2)); // ["X","Y"] (oldest -> newer)
console.log(b.getTail(2)); // ["B","Y"] (newest -> older)
// Pop
console.log(b.popHead()); // "X"
console.log(b.popTail(2)); // ["B","Y"] (newest -> older)
// Replace all (keeps last capacity if overflow)
b.replaceAll(["1", "2", "3", "4"]);
console.log(b.getAll()); // ["2","3","4"]
`
`ts
import { createBuffer } from "circular-queue-react";
const buf = createBuffer
buf.pushTail([1, 2, 3, 4]); // keeps [2,3,4]
`
useCircularBuffer provides a BufferManager-TAILed stateful hook:
- data auto-updates after mutations
- pushHead / pushTail / popHead / popTail / replaceAll / clear / resize
`tsx
import { useCircularBuffer } from "circular-queue-react";
export function LogViewer() {
const { data, pushTail, popHead, clear, size, capacity, available, isFull } =
useCircularBuffer
return (
{data.map((log, i) => (
#### Hook options
`ts
const { data } = useCircularBuffer(10, {
initialItems: [1, 2, 3, 4, 5],
});
`---
Order Semantics (Important)
This library uses consistent ordering rules:
-
getAll() always returns oldest â newest
- getHead(n) returns oldest â newer
- getTail(n) returns newest â older
- popHead(n) removes/returns oldest â newer
- popTail(n) removes/returns newest â older---
Important Type Limitation
â ïļ When
T itself is an array type, the array overload for pushHead/pushTail cannot be used.$3
TypeScript cannot distinguish between:
-
T (when T = number[])
- readonly T[] (which would be readonly number[][])Both resolve to array types, making overload resolution ambiguous.
$3
`ts
// â PROBLEMATIC: T = number[]
const buf = new BufferManager(5);// This will fail! TypeScript cannot tell if you mean:
// 1. Push a single item (which happens to be an array): number[]
// 2. Push multiple items: readonly number[][]
buf.pushTail([[1, 2], [3, 4]]);
// â
SOLUTION: Push one item at a time
buf.pushTail([1, 2]); // Push single array
buf.pushTail([3, 4]); // Push another single array
`$3
When
T is an array type, always push items one at a time instead of using the array overload:`ts
const items: number[][] = [[1, 2], [3, 4], [5, 6]];// â Don't do this:
// buf.pushTail(items);
// â
Do this instead:
for (const item of items) {
buf.pushTail(item);
}
// Or use a wrapper type:
type Item = { data: number[] };
const typedBuf = new BufferManager- (5);
typedBuf.pushTail([
{ data: [1, 2] },
{ data: [3, 4] }
]); // â
Works!
`This limitation applies to:
-
BufferManager.pushHead()
- BufferManager.pushTail()
- useCircularBuffer hook's pushHead and pushTail---
API Reference
$3
`ts
Direction.HEAD; // head / oldest side
Direction.TAIL; // tail / newest side
`$3
#### Constructor
-
new CircularBuffer#### Methods
-
push(item: T, direction: Direction): void
- pop(direction: Direction): T | undefined
- get(direction: Direction): T | undefined
- get(direction: Direction, count: number): T[] HEAD count: oldest â newer, TAIL count: newest â older-
clear(): void
- resize(newCapacity: number): void (logical capacity)
- getSize(): number
- getCapacity(): number (physical storage)
- getLogicalCapacity(): number
- [Symbol.iterator](): Iterator (oldest â newest)$3
High-level managed buffer built on top of CircularBuffer.
#### Add (Push)
-
pushHead(item: T): void
- pushHead(items: readonly T[]): void
- pushTail(item: T): void
- pushTail(items: readonly T[]): void#### Remove (Pop)
-
popHead(): T | undefined
- popHead(count: number): T[] (oldest â newer)
- popTail(): T | undefined
- popTail(count: number): T[] (newest â older)#### Peek (Read Without Removing)
-
getHead(): T | undefined
- getHead(count: number): T[] (oldest â newer)
- getTail(): T | undefined
- getTail(count: number): T[] (newest â older)
- getAll(): T[] (oldest â newest)#### Maintenance / Status
-
clear(): void
- resize(newCapacity: number): void
- replaceAll(items: readonly T[]): void
- size(): number
- capacity(): number
- isEmpty(): boolean
- isFull(): boolean
- available(): number#### Utilities
-
getFirstAndLast(): { first: T | undefined; last: T | undefined }
- getInfo(): { data: T[]; totalCount: number }
- forEach(cb): void
- map(cb): U[]
- filter(cb): T[]
- Iterable (oldest â newest)$3
`ts
function useCircularBuffer(
capacity: number,
options?: { initialItems?: readonly T[] }
): {
data: T[]; pushHead: (input: T | readonly T[]) => void;
pushTail: (input: T | readonly T[]) => void;
popHead: { (): T | undefined; (count: number): T[] };
popTail: { (): T | undefined; (count: number): T[] };
getHead: () => T | undefined;
getTail: () => T | undefined;
clear: () => void;
replaceAll: (items: readonly T[]) => void;
resize: (newCapacity: number) => void;
size: number;
capacity: number;
isEmpty: boolean;
isFull: boolean;
available: number;
getFirstAndLast: () => { first: T | undefined; last: T | undefined };
// advanced:
manager: BufferManager;
};
`---
Use Cases
$3
`tsx
import { useCircularBuffer } from "circular-queue-react";type LogEntry = {
ts: number;
level: "info" | "warn" | "error";
message: string;
};
export function LogViewer() {
const { data, pushTail, clear, size, isFull } =
useCircularBuffer(1000);
const add = (level: LogEntry["level"], message: string) =>
pushTail({ ts: Date.now(), level, message });
const errors = data.filter((x) => x.level === "error");
return (
Logs ({size}/1000) {isFull && "â ïļ FULL"}
Errors: {errors.length}
{data.map((x, i) => (
[{x.level}] {x.message}
))}
);
}
`$3
`ts
import { BufferManager } from "circular-queue-react";class RollingAverage {
private buf = new BufferManager(5);
add(v: number) {
this.buf.pushTail(v);
}
avg() {
const a = this.buf.getAll();
return a.length ? a.reduce((s, x) => s + x, 0) / a.length : 0;
}
}
`---
Performance
| Operation | Complexity |
| ------------------------------- | ---------- |
| push / pop / get (peek) | O(1) |
| get(count) | O(k) |
| getAll / iteration snapshot | O(n) |
| resize | O(n) |
| clear | O(1) |
---
Testing
This package includes comprehensive test coverage using Vitest and React Testing Library.
$3
`bash
Run all tests
npm testRun tests with coverage
npm run test:coverage
`$3
The project maintains high test coverage:
- CircularBuffer: 100% coverage (32 tests)
- BufferManager: 100% coverage (47 tests)
- React Hook (useCircularBuffer): 100% coverage (29 tests)
Total: 108 tests across 3 test suites
Coverage reports are generated in the
coverage/ directory. Open coverage/index.html` in your browser to view detailed coverage information.This project uses GitHub Actions for continuous integration:
- Automated Testing: Tests run on Node.js 18, 20, and 22
- Build Verification: Ensures TypeScript compilation succeeds
- Coverage Reporting: Automatic upload to Codecov
See .github/workflows/ci.yml for the complete CI configuration.
---
See CHANGELOG.md for version history and release notes.
MIT
PRs are welcome!
If you find a bug or want a feature, please open an issue.