Skip to main content
Home

Built and signed on GitHub Actions

Works with
This package works with Cloudflare Workers, Node.js, Deno, Bun, Browsers
This package works with Cloudflare Workers
This package works with Node.js
This package works with Deno
This package works with Bun
This package works with Browsers
JSR Score94%
Downloads2/wk
Published5 hours ago (0.7.0)

A Hyper-Performant GC-Minimizing UInt32Array-Backed Pool of Bits in Typescript

BitPool

A high-performance bit pool for managing resource allocation with efficient memory usage and fast bitwise operations.

MIT License Written in Typescript Deno version Bun version Node version

Features

  • High Performance: Optimized bitwise operations with minimal GC pressure
  • Memory Efficient: Uses Uint32Array backing for compact storage
  • Zero-Allocation Options: forEach* and *Into methods avoid allocations in hot paths
  • Batch Operations: acquireN, acquireNInto, and releaseAll for efficient bulk operations
  • Set Operations: intersect, union, difference, and symmetricDifference for combining pools
  • Type Safe: Full TypeScript support with comprehensive type definitions
  • Iterator Support: Built-in iterators for available and occupied indices
  • Flexible Construction: Create pools from scratch, arrays, or Uint32Array
  • Serialization: toUint32Array() and fromUint32Array() for save/restore workflows
  • Large Pools: Supports up to 536,870,911 indices (BitPool.MAX_SAFE_SIZE)

Installation

Node

npx jsr add @phughesmcr/bitpool
import { BitPool } from "@phughesmcr/bitpool";

Deno

deno add jsr:@phughesmcr/bitpool
import { BitPool } from "@phughesmcr/bitpool";

Bun

bunx jsr add @phughesmcr/bitpool
import { BitPool } from "@phughesmcr/bitpool";

Quick Start

import { BitPool } from "@phughesmcr/bitpool";

// Create a pool with 1000 indices (0-999)
const pool = new BitPool(1000);

// Acquire resources
const id1 = pool.acquire(); // 0
const id2 = pool.acquire(); // 1

console.log(pool.isOccupied(id1)); // true
console.log(pool.availableCount); // 998

// Release resources
pool.release(id1);
pool.release(id2);

console.log(pool.isOccupied(id1)); // false
console.log(pool.availableCount); // 1000

API Reference

Constructor

new BitPool(size: number)

Creates a new BitPool with the specified size.

const pool = new BitPool(1000);

Static Properties

BitPool.MAX_SAFE_SIZE

The maximum safe size of the pool (536,870,911 indices).

BitPool.MAX_SAFE_VALUE

The maximum safe value for a Uint32Array chunk (0xFFFFFFFF).

Static Methods

BitPool.fromArray(capacity: number, array: ArrayLike<number>)

Creates a BitPool from an array of uint32 values representing bit patterns.
Each uint32 value represents 32 bits where 1 = occupied, 0 = available.

// Create a pool with first 4 bits occupied
const pool = BitPool.fromArray(32, [0b00001111]);

BitPool.fromUint32Array(capacity: number, array: ArrayLike<number>)

Creates a BitPool from a Uint32Array or number array.

const mask = new Uint32Array([0xFFFF0000, 0x0000FFFF]);
const pool = BitPool.fromUint32Array(64, mask);

Properties

size: number

The total size of the pool.

const pool = new BitPool(1000);
console.log(pool.size); // 1000

availableCount: number

The number of available slots.

console.log(pool.availableCount); // Number of free slots

occupiedCount: number

The number of occupied slots.

console.log(pool.occupiedCount); // Number of used slots

nextAvailableIndex: number

The next available index, or -1 if the pool is full.

console.log(pool.nextAvailableIndex); // Next free index

isEmpty: boolean

Checks if the pool is empty (all slots available).

if (pool.isEmpty) {
  console.log("Pool is empty");
}

isFull: boolean

Checks if the pool is full (no slots available).

if (pool.isFull) {
  console.log("Pool is full");
}

Methods

acquire(): number

Acquires the next available index. Returns -1 if the pool is full.

const index = pool.acquire();
if (index !== -1) {
  console.log(`Acquired index ${index}`);
}

acquireN(count: number): number[]

Batch acquire multiple indices with all-or-nothing semantics. Throws if insufficient capacity.

const indices = pool.acquireN(10); // Acquire exactly 10 indices
// Throws RangeError if fewer than 10 are available

acquireNInto(out: Uint32Array): number

Zero-allocation batch acquire into a preallocated buffer. Returns count of indices actually acquired.

const buffer = new Uint32Array(100);
const count = pool.acquireNInto(buffer);
console.log(`Acquired ${count} indices`);

release(index: number): void

Releases an occupied index back to the pool. Uses LIFO semantics (released index becomes next available).

pool.release(index);

releaseAll(indices: Iterable<number>): void

Batch release multiple indices.

pool.releaseAll([0, 1, 2, 3]); // Release multiple indices
pool.releaseAll(acquiredSet);   // Works with Set, Array, or any Iterable

isAvailable(index: number): boolean

Checks if an index is available.

if (pool.isAvailable(42)) {
  console.log("Index 42 is available");
}

isOccupied(index: number): boolean

Checks if an index is occupied.

if (pool.isOccupied(42)) {
  console.log("Index 42 is occupied");
}

findNextAvailable(startIndex?: number, loop?: boolean): number

Finds the next available index starting from the specified index. Returns -1 if no available indices are found.

const next = pool.findNextAvailable(100); // Start search from index 100
const looped = pool.findNextAvailable(100, true); // Loop back to start if needed

clear(): void

Clears the pool, making all indices available.

pool.clear();
console.log(pool.isEmpty); // true

fill(): void

Fills the pool, marking all indices as occupied.

pool.fill();
console.log(pool.isFull); // true

refresh(): void

Refreshes the pool, ensuring the next available index is set to the first available index.

pool.refresh();

clone(): BitPool

Creates a copy of the pool.

const cloned = pool.clone();

toUint32Array(): Uint32Array

Returns a copy of the internal buffer as a Uint32Array. Use with fromUint32Array for serialization/deserialization.

const pool = new BitPool(64);
pool.acquire(); // 0
pool.acquire(); // 1
const serialized = pool.toUint32Array();

// Later...
const restored = BitPool.fromUint32Array(64, serialized);

Zero-Allocation Methods

These methods avoid allocations in hot paths by using callbacks or preallocated buffers.

forEachAvailable(callback: (index: number) => void, startIndex?: number, endIndex?: number): this

Zero-allocation iteration over available indices via callback.

pool.forEachAvailable((index) => {
  console.log(`Index ${index} is available`);
});

// With range
pool.forEachAvailable((index) => {
  console.log(`Index ${index} is available`);
}, 100, 200);

forEachOccupied(callback: (index: number) => void, startIndex?: number, endIndex?: number): this

Zero-allocation iteration over occupied indices via callback.

pool.forEachOccupied((index) => {
  console.log(`Index ${index} is occupied`);
});

forEachChunk(callback: (chunk: number, chunkIndex: number) => void): this

Zero-allocation iteration over raw uint32 chunk values.

pool.forEachChunk((chunk, i) => {
  console.log(`Chunk ${i}: ${chunk.toString(2)}`);
});

availableIndicesInto(out: Uint32Array, startIndex?: number, endIndex?: number): number

Copy available indices into a preallocated buffer. Returns number of indices written.

const buffer = new Uint32Array(pool.availableCount);
const count = pool.availableIndicesInto(buffer);

occupiedIndicesInto(out: Uint32Array, startIndex?: number, endIndex?: number): number

Copy occupied indices into a preallocated buffer. Returns number of indices written.

const buffer = new Uint32Array(pool.occupiedCount);
const count = pool.occupiedIndicesInto(buffer);

Set Operations

Binary set operations for combining BitPools. All require pools of equal size.

intersect(other: BitPool): BitPool

Returns a new BitPool containing only indices occupied in both pools (AND operation).

const a = new BitPool(32);
a.acquireN(4); // occupies 0, 1, 2, 3

const b = new BitPool(32);
b.acquireN(2); // occupies 0, 1

const intersection = a.intersect(b); // occupies 0, 1

union(other: BitPool): BitPool

Returns a new BitPool containing indices occupied in either pool (OR operation).

const a = new BitPool(32);
a.acquireN(2); // occupies 0, 1

const b = new BitPool(32);
b.acquire(); b.acquire(); b.acquire(); b.acquire();
b.release(0); b.release(1); // occupies 2, 3

const combined = a.union(b); // occupies 0, 1, 2, 3

difference(other: BitPool): BitPool

Returns a new BitPool containing indices occupied in this pool but not in the other (AND NOT operation).

const a = new BitPool(32);
a.acquireN(4); // occupies 0, 1, 2, 3

const b = new BitPool(32);
b.acquireN(2); // occupies 0, 1

const diff = a.difference(b); // occupies 2, 3 (in a but not in b)

symmetricDifference(other: BitPool): BitPool

Returns a new BitPool containing indices occupied in exactly one of the pools (XOR operation).

const a = new BitPool(32);
a.acquireN(3); // occupies 0, 1, 2

const b = new BitPool(32);
b.acquire(); b.acquire(); b.acquire(); b.acquire();
b.release(0); // occupies 1, 2, 3

const symDiff = a.symmetricDifference(b); // occupies 0, 3 (in one but not both)

Iterators

availableIndices(startIndex?: number, endIndex?: number): IterableIterator<number>

Iterator that yields all available indices in the specified range.

for (const index of pool.availableIndices()) {
  console.log(`Index ${index} is available`);
}

// Iterate over a specific range
for (const index of pool.availableIndices(100, 200)) {
  console.log(`Index ${index} is available`);
}

occupiedIndices(startIndex?: number, endIndex?: number): IterableIterator<number>

Iterator that yields all occupied indices in the specified range.

for (const index of pool.occupiedIndices()) {
  console.log(`Index ${index} is occupied`);
}

// Iterate over a specific range
for (const index of pool.occupiedIndices(100, 200)) {
  console.log(`Index ${index} is occupied`);
}

[Symbol.iterator](): IterableIterator<number>

Iterator that yields the underlying Uint32Array values.

for (const chunk of pool) {
  console.log(`Chunk value: ${chunk}`);
}

Use Cases

Port Management

const TOTAL_PORTS = 65_536;
const RESERVED_UNTIL = 49_152;

// Create availability mask for ephemeral ports
function buildPortMask(capacity: number, reservedUntil: number): Uint32Array {
  const chunkCount = Math.ceil(capacity / 32);
  const mask = new Uint32Array(chunkCount);
  
  for (let i = reservedUntil; i < capacity; i++) {
    const chunkIndex = i >>> 5;
    const bitPosition = i & 31;
    mask[chunkIndex] |= (1 << bitPosition);
  }
  return mask;
}

const portMask = buildPortMask(TOTAL_PORTS, RESERVED_UNTIL);
const portPool = BitPool.fromUint32Array(TOTAL_PORTS, portMask);

const port = portPool.acquire(); // Get an ephemeral port
portPool.release(port); // Release it back

Entity ID Management

const entityPool = new BitPool(10_000);

// Acquire IDs for new entities
const playerId = entityPool.acquire();
const enemyId = entityPool.acquire();

// Release IDs when entities are destroyed
entityPool.release(playerId);
entityPool.release(enemyId);

Connection Pool

const connectionPool = new BitPool(100);

function getConnection(): number {
  const connId = connectionPool.acquire();
  if (connId === -1) {
    throw new Error("Connection pool exhausted");
  }
  return connId;
}

function releaseConnection(connId: number): void {
  connectionPool.release(connId);
}

Serialization / Persistence

const pool = new BitPool(1000);

// Use the pool...
pool.acquireN(50);

// Save state
const state = pool.toUint32Array();
localStorage.setItem("poolState", JSON.stringify(Array.from(state)));

// Restore state
const saved = JSON.parse(localStorage.getItem("poolState")!);
const restored = BitPool.fromUint32Array(1000, new Uint32Array(saved));

Performance

BitPool is designed for high-performance scenarios with minimal GC pressure:

  • Backed by Uint32Array for efficient memory usage
  • Optimized bitwise operations for fast lookups
  • Zero-allocation methods: forEachAvailable, forEachOccupied, forEachChunk, availableIndicesInto, occupiedIndicesInto, acquireNInto
  • Allocating methods: Generator iterators (availableIndices, occupiedIndices, Symbol.iterator) and acquireN allocate objects
  • LIFO release behavior provides cache-friendly reuse patterns
  • Efficient search algorithms for finding available slots

Run deno task example to see a performance demonstration with ephemeral port allocation.

Contributing

Contributions are welcome! The aim of the project is performance - both in terms of speed and GC allocation pressure.

Please run deno test and deno task prep before committing.

Documentation

See jsr.io/@phughesmcr/bitpool for complete API documentation.

License

BitPool is released under the MIT license. See LICENSE for further details.

© 2025 The BitPool Authors. All rights reserved.

See AUTHORS.md for author details.

Built and signed on
GitHub Actions

Report package

Please provide a reason for reporting this package. We will review your report and take appropriate action.

Please review the JSR usage policy before submitting a report.

Add Package

deno add jsr:@phughesmcr/bitpool

Import symbol

import * as bitpool from "@phughesmcr/bitpool";
or

Import directly with a jsr specifier

import * as bitpool from "jsr:@phughesmcr/bitpool";

Add Package

pnpm i jsr:@phughesmcr/bitpool
or (using pnpm 10.8 or older)
pnpm dlx jsr add @phughesmcr/bitpool

Import symbol

import * as bitpool from "@phughesmcr/bitpool";

Add Package

yarn add jsr:@phughesmcr/bitpool
or (using Yarn 4.8 or older)
yarn dlx jsr add @phughesmcr/bitpool

Import symbol

import * as bitpool from "@phughesmcr/bitpool";

Add Package

vlt install jsr:@phughesmcr/bitpool

Import symbol

import * as bitpool from "@phughesmcr/bitpool";

Add Package

npx jsr add @phughesmcr/bitpool

Import symbol

import * as bitpool from "@phughesmcr/bitpool";

Add Package

bunx jsr add @phughesmcr/bitpool

Import symbol

import * as bitpool from "@phughesmcr/bitpool";