The Codex / JavaScript / WeakRef & FinalizationRegistry

WeakRef & FinalizationRegistry

Weak references that don't prevent garbage collection, and callbacks that fire when objects are collected — when and why to use them.

What it is, in plain English: A WeakRef holds a reference to an object without preventing it from being garbage collected. If no other references to the object exist, the garbage collector can reclaim it, and WeakRef.deref() will return undefined. A FinalizationRegistry lets you register a callback that fires after an object has been collected — useful for cleanup. Both are advanced tools: most code should use WeakMap and WeakSet instead.

References that step aside for the garbage collector

Normally, if you have a variable pointing to an object, that object stays in memory forever — as long as your variable exists. This is called a "strong reference." A weak reference is different: it points to an object but doesn't prevent the garbage collector from deleting it if nothing else holds a strong reference.

When do you actually need this? Rarely. WeakMap and WeakSet already cover the most common use cases (associating metadata with objects without memory leaks). WeakRef and FinalizationRegistry are for advanced scenarios: caches, resource tracking, or native bridge cleanup.

WeakRef — check if an object is still alive

// Create a WeakRef to an object:
let server = { host: "localhost", port: 3000 };
const ref  = new WeakRef(server);

// .deref() returns the object if it's still alive, or undefined if GC'd:
const alive = ref.deref();
if (alive) {
  console.log(`Server: ${alive.host}:${alive.port}`);  // "Server: localhost:3000"
} else {
  console.log("Server was collected");
}

// Always use ?. with deref() — never assume it's alive:
console.log(ref.deref()?.host);   // "localhost" (or undefined if collected)

FinalizationRegistry — get notified when an object is collected

// Register a callback to fire when an object is garbage collected:
const registry = new FinalizationRegistry((heldValue) => {
  // 'heldValue' is what you passed when registering — NOT the object itself
  // (the object is already gone when this fires!)
  console.log(`Cleanup for: ${heldValue}`);
});

function createResource(name) {
  const resource = { name, data: new Array(10000).fill(0) };
  registry.register(resource, name);  // second arg = heldValue for the callback
  return resource;
}

let r = createResource("database");
// ... use r ...
r = null;  // release strong reference
// At some future point, GC runs and the callback fires:
// "Cleanup for: database"

The timing problem — why this is tricky

// Garbage collection is non-deterministic.
// You cannot predict WHEN the callback fires.
// You cannot force it to run.
// In some implementations it may never run (e.g. very short programs).

// This means FinalizationRegistry callbacks:
// ✓ Can do logging, metrics, optional cleanup
// ✗ Cannot be relied on for critical cleanup (use try/finally instead)
// ✗ Cannot be used for security-sensitive cleanup
// ✗ Cannot guarantee ordering relative to other code
Try It Yourself

Official Sources

The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.