How WeakSet Tracks Object References in JavaScript

JavaScript’s WeakSet is a specialized collection that holds unique objects without preventing the JavaScript engine from reclaiming their memory. This article explains how WeakSet leverages weak references, how the garbage collector determines object reachability, why WeakSet is not iterable, and how it safely tracks objects without creating memory leaks.

Strong References vs. Weak References

In standard JavaScript collections like Array or Set, adding an object creates a strong reference. As long as the collection exists in memory, the objects inside it cannot be garbage collected, even if all other variables pointing to them are set to null or go out of scope.

let user = { name: "Alice" };
const strongSet = new Set();

strongSet.add(user);
user = null; // The object { name: "Alice" } remains in memory because strongSet holds a strong reference.

A WeakSet, by contrast, holds only weak references to objects. A weak reference does not prevent the garbage collector from disposing of the referenced object if no other strong references to it exist.

let user = { name: "Alice" };
const weakSet = new WeakSet();

weakSet.add(user);
user = null; // The object { name: "Alice" } is now eligible for garbage collection.

How the Garbage Collector Treats WeakSet

Modern JavaScript engines (like V8 in Chrome and Node.js, SpiderMonkey in Firefox, and JavaScriptCore in Safari) use a Mark-and-Sweep garbage collection algorithm to manage memory.

  1. Roots Definition: The engine identifies “root” objects that are always reachable (global variables, currently executing functions, call stacks).
  2. Traversing References: The collector traverses the graph of references originating from the roots, marking every reachable object as alive.
  3. Weak Reference Exclusion: When the collector inspects a WeakSet, it does not count the links from the WeakSet to its elements as valid paths for reachability.
  4. Sweeping: If an object stored in a WeakSet has no incoming strong references originating from any root, the garbage collector treats it as dead and reclaims its memory. The corresponding entry inside the WeakSet is automatically invalidated.

Engine-Level Implementation: Ephemerons

Under the hood, JavaScript engines implement weak collections using structures similar to ephemerons or hidden internal properties.

Instead of maintaining an independent, persistent list of pointers that force objects to stay alive, the WeakSet is conceptually attached to the lifecycle of the objects it stores:

The Cost of Non-Determinism: Why WeakSet Has No Iteration

Because garbage collection is non-deterministic—meaning it runs at unpredictable intervals determined by memory pressure and CPU idle time—the exact moment an object is removed from memory cannot be predicted by code.

To prevent non-deterministic program behavior: - WeakSet does not have a .size property. - WeakSet does not implement keys(), values(), entries(), or the Symbol.iterator method. - WeakSet cannot be looped over using for...of or .forEach().

If iteration were allowed, the number and identity of elements in the set would change randomly depending on whether the garbage collector had executed, leading to inconsistent application state.

Only Objects Allowed

A WeakSet can only contain objects (and non-registered symbols in newer ECMAScript specifications). Primitive types such as numbers, strings, or booleans cannot be stored because primitives are immutable values passed by value, not tracked references managed by the garbage collector.

Common Use Cases

WeakSet is primarily used to tag or classify objects without modifying them or leaking memory: