WeakSet
new WeakSet([iterable]) | weakset.add(value) | weakset.delete(value) | weakset.has(value) A WeakSet is a JavaScript built-in object that stores collections of objects with weak references. Unlike regular Sets, WeakSets only hold object references—they cannot contain primitive values like strings, numbers, or booleans. The key feature of WeakSet is its “weak” hold on stored objects: if an object in the WeakSet has no other references pointing to it, it becomes eligible for garbage collection and will be automatically removed from the WeakSet. This makes WeakSet particularly useful for tracking objects without preventing their memory from being freed, though it also means WeakSets are not enumerable and have no reliable way to count their contents.
Syntax
new WeakSet([iterable])
TypeScript Signatures
The constructor accepts an optional iterable of objects and adds each element to the new WeakSet. The three methods follow a predictable shape: add returns the WeakSet itself for chaining, delete returns a boolean indicating whether removal succeeded, and has tests membership. Every argument and return type is generic over T extends object, which is how TypeScript enforces the object-only constraint at compile time.
interface WeakSet<T extends object = object> {
constructor(iterable?: Iterable<T>): WeakSet<T>;
add(value: T): this;
delete(value: T): boolean;
has(value: T): boolean;
}
Parameters
| Parameter | Type | Description |
|---|---|---|
| iterable | Iterable<object> | Optional. An iterable object whose elements will be added to the new WeakSet. Each element must be an object. |
Return Value
add()returns the WeakSet object (allows chaining)delete()returnstrueif an element was successfully deleted,falseif the element didn’t existhas()returnstrueif the value exists in the WeakSet,falseotherwise
Examples
Creating and using a WeakSet
const ws = new WeakSet();
const obj1 = { name: "first" };
const obj2 = { name: "second" };
// Add objects to the WeakSet
ws.add(obj1);
ws.add(obj2);
// Check if objects exist
console.log(ws.has(obj1)); // Output: true
console.log(ws.has(obj2)); // Output: true
// Delete an object
ws.delete(obj1);
console.log(ws.has(obj1)); // Output: false
console.log(ws.has(obj2)); // Output: true
WeakSet with Iterable
You can seed a WeakSet at construction time by passing an iterable of objects — most commonly an array. This avoids calling add() repeatedly when you already have a group of objects to track. Every element in the iterable must be an object, or the constructor throws a TypeError.
const obj1 = { id: 1 };
const obj2 = { id: 2 };
const obj3 = { id: 3 };
// Initialize with an iterable (array of objects)
const ws = new WeakSet([obj1, obj2, obj3]);
console.log(ws.has(obj1)); // Output: true
console.log(ws.has(obj2)); // Output: true
console.log(ws.has(obj3)); // Output: true
Memory management behavior
The defining feature of a WeakSet is that it does not prevent garbage collection. When the only remaining reference to an object is the one held by the WeakSet, that object becomes eligible for collection and is silently removed. You cannot observe this removal directly — JavaScript provides no event or callback — but the net effect is that memory is freed without any manual cleanup on your part.
const ws = new WeakSet();
let obj = { data: "temporary data" };
ws.add(obj);
console.log(ws.has(obj)); // Output: true
// Remove the only reference to the object
obj = null;
// At this point, the object becomes garbage collectable
// and will be removed from the WeakSet automatically
// (though JavaScript doesn't provide a way to observe this directly)
Common Patterns
Tracking visited objects in recursion
WeakSet is a natural fit for cycle detection in recursive algorithms. You pass a WeakSet through the call stack and check membership before descending into each object. Because the set holds weak references, you never need to worry about it keeping large object graphs alive after the traversal finishes — the WeakSet and its contents can be garbage-collected together once the top-level call returns.
function processObject(obj, visited = new WeakSet()) {
// Skip if already visited
if (visited.has(obj)) {
return;
}
// Mark as visited
visited.add(obj);
// Process the object
console.log("Processing:", obj);
// Recursively process nested objects
if (typeof obj === "object" && obj !== null) {
for (const key in obj) {
processObject(obj[key], visited);
}
}
}
const circular = { a: 1 };
circular.self = circular; // Circular reference
processObject(circular);
// Output: Processing: { a: 1, self: [Circular] }
Temporary object tracking
A common pattern is wrapping a WeakSet inside a class to track DOM elements or other objects whose lifetime you do not control. The class exposes track, isTracked, and untrack methods, but the underlying storage never prevents the tracked objects from being collected. When an element is removed from the DOM and all external references disappear, it vanishes from the tracker automatically.
class DOMTracker {
constructor() {
this.tracked = new WeakSet();
}
track(element) {
this.tracked.add(element);
return this;
}
isTracked(element) {
return this.tracked.has(element);
}
untrack(element) {
return this.tracked.delete(element);
}
}
const tracker = new DOMTracker();
const button = document.createElement("button");
tracker.track(button);
console.log(tracker.isTracked(button)); // Output: true
// When button is removed from DOM and all references are cleared,
// it will be garbage collected automatically
Avoiding circular references in data processing
Serialization and deep-cloning routines often encounter objects that reference themselves. A WeakSet can record which objects have already been processed without keeping those objects pinned in memory after the operation ends. If a visited object reappears during recursion, you can skip it or substitute a placeholder, avoiding infinite loops without any manual memory management.
function deepClone(obj, seen = new WeakSet()) {
// Handle primitives
if (typeof obj !== "object" || obj === null) {
return obj;
}
// Detect circular references
if (seen.has(obj)) {
return undefined; // Or handle appropriately
}
// Add to seen set
seen.add(obj);
// Clone the object
const cloned = Array.isArray(obj) ? [] : {};
for (const key in obj) {
cloned[key] = deepClone(obj[key], seen);
}
return cloned;
}
const original = { nested: { value: 42 } };
original.circular = original;
const cloned = deepClone(original);
console.log(cloned.nested.value); // Output: 42
When WeakSet is the right fit
Use WeakSet when you need to remember which objects you have already seen, but you do not want that bookkeeping to keep the objects alive. That makes it a good fit for DOM nodes, recursive tree walks, and transient caches. The important detail is that membership is tied to object identity, not object shape, so the same object reference can be tracked while a lookalike object is treated as new.
That behavior is different from a normal Set, which is enumerable and can store any value. WeakSet is intentionally more limited. You do not get size, iteration, or a snapshot of the contents. Those missing features are the tradeoff for garbage-collection friendliness, which is exactly why the type exists.
WeakSet vs Set
If you need to list every member, count entries, or serialize the collection, choose Set. If you only need temporary membership checks on objects, WeakSet is usually a better match. A normal Set can accidentally become a memory sink in long-running applications because it keeps strong references to everything you add. A WeakSet avoids that problem by design.
This means WeakSet works best as a sidecar data structure. It should support another algorithm rather than become the primary source of truth. For example, it can mark objects as visited during a traversal, or record which elements have been initialized, without taking ownership of those objects.
Garbage collection caveat
The automatic cleanup behavior is real, but it is also intentionally hard to observe. JavaScript does not let you ask exactly when a specific object will disappear from a WeakSet, because that timing belongs to the garbage collector. Code that depends on that moment will be fragile. Instead, think of WeakSet as a memory-aware membership test and avoid using it when you need deterministic accounting.
Next Steps
Now that you understand how WeakSet works, explore how it compares to other collection types and when to use each.
See Also
- Set — Regular Set collection for storing unique values
- WeakMap — Similar weak-reference collection for key-value pairs
- Map — Key-value pairs with strong references
- Set.prototype.has() — Check membership in a regular Set