WeakMap and WeakSet hold weak references to objects, allowing garbage collection when no other references exist. Here's how to use them.
WeakMap Basics#
1// Create WeakMap
2const weakMap = new WeakMap();
3
4// Only objects as keys (no primitives)
5const obj = { id: 1 };
6const func = () => {};
7const arr = [1, 2, 3];
8
9weakMap.set(obj, 'object data');
10weakMap.set(func, 'function data');
11weakMap.set(arr, 'array data');
12
13console.log(weakMap.get(obj)); // 'object data'
14console.log(weakMap.has(func)); // true
15
16weakMap.delete(arr);
17console.log(weakMap.has(arr)); // false
18
19// Primitives not allowed
20// weakMap.set('string', 'data'); // TypeErrorPrivate Data Pattern#
1// Store private data for class instances
2const privateData = new WeakMap();
3
4class User {
5 constructor(name, password) {
6 // Public data
7 this.name = name;
8
9 // Private data - not accessible from outside
10 privateData.set(this, {
11 password,
12 loginAttempts: 0,
13 });
14 }
15
16 checkPassword(attempt) {
17 const data = privateData.get(this);
18 data.loginAttempts++;
19
20 if (data.loginAttempts > 3) {
21 throw new Error('Too many attempts');
22 }
23
24 return data.password === attempt;
25 }
26
27 getLoginAttempts() {
28 return privateData.get(this).loginAttempts;
29 }
30}
31
32const user = new User('john', 'secret123');
33console.log(user.name); // 'john'
34console.log(user.password); // undefined
35console.log(user.checkPassword('wrong')); // falseCaching with WeakMap#
1// Cache computed values without memory leaks
2const cache = new WeakMap();
3
4function expensiveOperation(obj) {
5 if (cache.has(obj)) {
6 console.log('Cache hit');
7 return cache.get(obj);
8 }
9
10 console.log('Computing...');
11 const result = {
12 computed: Object.keys(obj).length,
13 timestamp: Date.now(),
14 };
15
16 cache.set(obj, result);
17 return result;
18}
19
20const data = { a: 1, b: 2, c: 3 };
21
22expensiveOperation(data); // Computing...
23expensiveOperation(data); // Cache hit
24
25// When data is no longer referenced,
26// cache entry is automatically garbage collectedDOM Element Data#
1// Associate data with DOM elements
2const elementData = new WeakMap();
3
4function setElementData(element, data) {
5 const existing = elementData.get(element) || {};
6 elementData.set(element, { ...existing, ...data });
7}
8
9function getElementData(element) {
10 return elementData.get(element) || {};
11}
12
13// Usage
14const button = document.querySelector('#myButton');
15setElementData(button, { clicks: 0, lastClicked: null });
16
17button.addEventListener('click', () => {
18 const data = getElementData(button);
19 setElementData(button, {
20 clicks: data.clicks + 1,
21 lastClicked: Date.now(),
22 });
23});
24
25// When button is removed from DOM and no longer referenced,
26// associated data is automatically cleaned upWeakSet Basics#
1// Create WeakSet
2const weakSet = new WeakSet();
3
4const obj1 = { id: 1 };
5const obj2 = { id: 2 };
6
7weakSet.add(obj1);
8weakSet.add(obj2);
9
10console.log(weakSet.has(obj1)); // true
11console.log(weakSet.has({ id: 1 })); // false (different object)
12
13weakSet.delete(obj1);
14console.log(weakSet.has(obj1)); // false
15
16// No iteration, no size property
17// weakSet.forEach() // Error
18// weakSet.size // undefinedObject Tracking#
1// Track which objects have been processed
2const processed = new WeakSet();
3
4function processOnce(obj) {
5 if (processed.has(obj)) {
6 console.log('Already processed');
7 return;
8 }
9
10 // Process the object
11 console.log('Processing:', obj);
12 // ... do work ...
13
14 processed.add(obj);
15}
16
17const item = { data: 'test' };
18processOnce(item); // Processing: { data: 'test' }
19processOnce(item); // Already processedCircular Reference Detection#
1// Detect circular references during traversal
2function deepClone(obj, seen = new WeakSet()) {
3 if (obj === null || typeof obj !== 'object') {
4 return obj;
5 }
6
7 if (seen.has(obj)) {
8 throw new Error('Circular reference detected');
9 }
10
11 seen.add(obj);
12
13 if (Array.isArray(obj)) {
14 return obj.map((item) => deepClone(item, seen));
15 }
16
17 const clone = {};
18 for (const key in obj) {
19 if (obj.hasOwnProperty(key)) {
20 clone[key] = deepClone(obj[key], seen);
21 }
22 }
23
24 return clone;
25}
26
27// Test
28const a = { name: 'a' };
29const b = { name: 'b', ref: a };
30a.ref = b; // Circular reference
31
32try {
33 deepClone(a); // Error: Circular reference detected
34} catch (e) {
35 console.log(e.message);
36}Instance Validation#
1// Track valid instances
2const validInstances = new WeakSet();
3
4class SecureToken {
5 constructor(value) {
6 this.value = value;
7 validInstances.add(this);
8 }
9
10 static isValid(token) {
11 return validInstances.has(token);
12 }
13
14 revoke() {
15 validInstances.delete(this);
16 }
17}
18
19function useToken(token) {
20 if (!SecureToken.isValid(token)) {
21 throw new Error('Invalid or revoked token');
22 }
23 return token.value;
24}
25
26const token = new SecureToken('secret');
27console.log(useToken(token)); // 'secret'
28
29token.revoke();
30useToken(token); // Error: Invalid or revoked tokenEvent Listener Tracking#
1// Track elements with listeners to prevent duplicates
2const hasListener = new WeakSet();
3
4function addClickHandler(element, handler) {
5 if (hasListener.has(element)) {
6 console.log('Listener already added');
7 return;
8 }
9
10 element.addEventListener('click', handler);
11 hasListener.add(element);
12}
13
14function removeClickHandler(element, handler) {
15 if (!hasListener.has(element)) {
16 return;
17 }
18
19 element.removeEventListener('click', handler);
20 hasListener.delete(element);
21}Memoization with Object Keys#
1// Memoize function calls with object arguments
2function memoize(fn) {
3 const cache = new WeakMap();
4
5 return function (obj) {
6 if (cache.has(obj)) {
7 return cache.get(obj);
8 }
9
10 const result = fn(obj);
11 cache.set(obj, result);
12 return result;
13 };
14}
15
16// Usage
17const getKeys = memoize((obj) => {
18 console.log('Computing keys...');
19 return Object.keys(obj);
20});
21
22const config = { a: 1, b: 2, c: 3 };
23
24getKeys(config); // Computing keys... -> ['a', 'b', 'c']
25getKeys(config); // Returns cached ['a', 'b', 'c']Metadata Association#
1// Associate metadata without modifying objects
2const metadata = new WeakMap();
3
4function setMeta(obj, key, value) {
5 if (!metadata.has(obj)) {
6 metadata.set(obj, new Map());
7 }
8 metadata.get(obj).set(key, value);
9}
10
11function getMeta(obj, key) {
12 return metadata.get(obj)?.get(key);
13}
14
15function getAllMeta(obj) {
16 const meta = metadata.get(obj);
17 return meta ? Object.fromEntries(meta) : {};
18}
19
20// Usage
21const user = { name: 'John' };
22setMeta(user, 'created', Date.now());
23setMeta(user, 'source', 'api');
24
25console.log(getMeta(user, 'created'));
26console.log(getAllMeta(user)); // { created: ..., source: 'api' }Comparison Table#
1/*
2Feature | WeakMap | WeakSet | Map | Set
3---------------------|----------------|----------------|----------------|----------------
4Keys/Values | object → any | objects only | any → any | any
5Garbage Collection | Yes | Yes | No | No
6Iteration | No | No | Yes | Yes
7Size property | No | No | Yes | Yes
8Use case | Private data | Object flags | General cache | Unique values
9*/When to Use#
1// Use WeakMap when:
2// 1. Storing private data for objects
3// 2. Caching based on object identity
4// 3. Associating data with DOM elements
5// 4. Preventing memory leaks with object keys
6
7// Use WeakSet when:
8// 1. Tracking object state (processed/visited)
9// 2. Instance validation
10// 3. Preventing duplicate operations
11// 4. Circular reference detection
12
13// Use regular Map/Set when:
14// 1. Need iteration
15// 2. Need size
16// 3. Need primitive keys
17// 4. Want to prevent garbage collectionBest Practices#
WeakMap:
✓ Private class data
✓ Object-keyed caches
✓ DOM element associations
✓ Metadata storage
WeakSet:
✓ Object tracking flags
✓ Instance validation
✓ Visited/processed tracking
✓ Circular detection
Memory:
✓ Let GC handle cleanup
✓ Don't hold other references
✓ Use for large object graphs
✓ Consider for event systems
Avoid:
✗ Expecting iteration
✗ Using for primitives
✗ Relying on size
✗ Overcomplicating simple cases
Conclusion#
WeakMap and WeakSet are specialized collections for object-only keys that don't prevent garbage collection. Use WeakMap for private data, caching, and metadata association. Use WeakSet for tracking object state and validation. They're essential for preventing memory leaks in long-running applications, especially when working with DOM elements or large object graphs.