-
-
Notifications
You must be signed in to change notification settings - Fork 0
FreeList
Free list / object pool — recycles objects by threading dead ones into an intrusive singly linked list through their own link property. The pool itself allocates nothing; recycled nodes come out ready for adoption by the toolkit's lists.
Legend for tables
-
API
-
options- an object with optional properties:-
create()- factory used byacquire()when the pool is empty. Omit for a pure recycler. -
reset(node)- hook called on everyrelease()before pooling — scrub payload references here. -
capacity- maximum number of pooled objects; releases beyond it are dropped for GC. Default:Infinity;0disables pooling. -
nextName- property name (string or symbol) threading the free list. Default:'next'. -
prevName- optional second link property scrubbed onacquire()— set it (e.g.'prev') when pooling DLL nodes.
-
-
node- a pooled object
-
-
Complexity
- O - complexity of an operation
- O(1) - constant time
- O(n) - linear time proportional to the pool size
- O(k) - linear time proportional to the argument
Implementation of FreeList:
import FreeList from 'list-toolkit/free-list.js';
import ValueList from 'list-toolkit/value-list.js';
import {ValueNode} from 'list-toolkit/list/nodes.js';
const pool = new FreeList({
create: () => new ValueNode(null),
reset: node => (node.value = null), // don't pin payloads while pooled
prevName: 'prev' // ValueNode is a DLL node
});
const queue = new ValueList();
// push through the pool:
const node = pool.acquire();
node.value = task;
queue.pushBackNode(node);
// pop through the pool:
const done = queue.popFrontNode();
use(done.value);
pool.release(done);Methods:
| Member | Return type | Description | O |
|---|---|---|---|
constructor(options) |
this |
constructs a new FreeList
|
O(1) |
size |
number | the number of pooled objects | O(1) |
capacity |
number | the pool bound, or Infinity
|
O(1) |
isEmpty |
boolean | checks if the pool is empty | O(1) |
isFull |
boolean | checks if the pool reached its capacity | O(1) |
acquire() |
node or undefined
|
take a pooled object, or create() one; links come out nulled |
O(1) |
release(node) |
this |
run reset, then pool the object (or drop it when full) |
O(1) |
preallocate(count) |
this |
fill the pool with freshly created objects | O(k) |
clear() |
this |
drop all pooled objects (threading links nulled) | O(n) |
[Symbol.iterator]() |
iterator | iterate over the pooled objects | O(1) |
The pool is intrusive: the free list lives in the pooled objects' own link property (nextName, default 'next') — dead nodes carry the pool structure, exactly like live nodes carry list structure. Recycled objects leave acquire() with nextName (and prevName, when configured) set to null, which the toolkit's lists adopt directly via pushFrontNode/pushBackNode.
Always scrub payloads in reset — a pooled ValueNode still holding its value would pin that object for the garbage collector as long as it sits in the pool.
When it pays off — honest guidance (measured on bench/bench-pool.js, 2026-07-17): on a raw push/pop churn microbench, pooling is ~15% slower than fresh allocation (676μs vs 586μs per 10k cycles) — modern engines allocate short-lived objects with a pointer bump, and the pooled path adds bookkeeping. A pool is not a throughput optimization; reach for it to reduce GC pressure and pause jitter in long-running processes with steady churn, to reuse objects that are expensive to construct, or to cap allocation in latency-sensitive loops. Measure in your real workload, not a microbench.
release() does not verify the object is outside a list — releasing a node that is still linked corrupts both structures (same ownership contract as list nodes).
FreeList is the default export and is also exported by name.
Concepts
DLL: doubly linked lists
SLL: singly linked lists
Unrolled list
List utilities
Caches
Heaps
Queue, Stack, and Deque
Trees
Skip list
Timer wheel
Free list