WeakMap and WeakRef
In most languages, holding a reference to an object keeps it alive in memory. JavaScript's WeakMap and WeakRef break that rule—they let you reference objects without preventing garbage collection. Over 5 years shipping production systems, I've found these structures invaluable for metadata attachment, caching, and leak prevention, but they demand a careful mindset shift. Today we'll explore when and how to use them effectively.
The Problem with Strong References
Every time you store an object in a regular Map, array, or variable, you create a strong reference. The JavaScript engine will never garbage-collect that object as long as the reference exists. This is usually what we want—we hold references to things we care about. But there are cases where we want to attach metadata to an object without keeping it alive.
Imagine you're building a framework that tracks which DOM nodes have event listeners attached. If you store that mapping in a regular Map, you'll leak memory: nodes that are removed from the DOM can't be garbage-collected because your Map still holds them. That's where WeakMap enters the picture.
WeakMap Keys Can Be Collected
A WeakMap holds key-value pairs, but only if the key is an object, and crucially, the key can be garbage-collected. When there are no other references to the key object, the entry disappears from the WeakMap automatically. The garbage collector doesn't count WeakMap references as "keeping the object alive."
// Regular Map: keeps keys alive
const regularMap = new Map();
let user = { id: 1, name: 'Alice' };
regularMap.set(user, 'metadata');
user = null; // user object stays in memory because regularMap holds it
console.log(regularMap.size); // Still 1
// WeakMap: keys can be garbage-collected
const weakMap = new WeakMap();
let user2 = { id: 2, name: 'Bob' };
weakMap.set(user2, 'metadata');
user2 = null; // user2 object CAN be collected even though weakMap references it
// We can't check size, but the entry is gone
Key constraint: WeakMap keys must be objects. You can't use primitives (strings, numbers, symbols) as keys. You also can't iterate over a WeakMap—there's no .keys(), .values(), or .entries() method. This prevents you from relying on the contents being stable.
Attach Metadata to Objects Safely
The most practical use case I've seen is attaching private metadata to objects without modifying the object itself or leaking memory. Consider a React-like component system that needs to store internal state keyed by component instance.
class ComponentRegistry {
private metadata = new WeakMap();
registerComponent(component: any, config: any) {
// Store config metadata keyed by the component instance
this.metadata.set(component, {
mounted: false,
subscriptions: [],
...config,
});
}
getMetadata(component: any) {
return this.metadata.get(component);
}
updateMetadata(component: any, updates: any) {
const current = this.metadata.get(component) || {};
this.metadata.set(component, { ...current, ...updates });
}
}
// Usage
const registry = new ComponentRegistry();
let myComponent = { render: () => {} };
registry.registerComponent(myComponent, { priority: 'high' });
console.log(registry.getMetadata(myComponent)); // { mounted: false, priority: 'high', ... }
// When myComponent is no longer referenced elsewhere, it can be garbage-collected
// and the metadata entry vanishes from the WeakMap automatically
myComponent = null;
This pattern is used in production frameworks for DOM node tracking, event listener bookkeeping, and component lifecycle management. Because the WeakMap doesn't keep the object alive, you don't have to manually clean up entries when objects are removed.
Another real-world example: observability. When you wrap objects with a proxy to track access patterns, store the proxy metadata in a WeakMap keyed by the original object. If the original is garbage-collected, so is the tracking data.
WeakRef for Caches That Can Drop Entries
WeakRef is a wrapper around an object that doesn't prevent it from being garbage-collected. It lets you store references in a regular collection (like a Map or array) while allowing the target to be collected. This is ideal for caches where you're happy to evict entries when memory is tight.
class WeakCache {
private cache = new Map();
set(key: string, value: any) {
// Store a WeakRef to the value, not the value itself
this.cache.set(key, new WeakRef(value));
}
get(key: string) {
const ref = this.cache.get(key);
if (!ref) return undefined;
const value = ref.deref(); // deref() returns the object or undefined if collected
if (value === undefined) {
// Object was garbage-collected; clean up
this.cache.delete(key);
}
return value;
}
}
// Usage
const cache = new WeakCache();
let expensiveObject = { data: new Array(1000000).fill('x') };
cache.set('result', expensiveObject);
console.log(cache.get('result')); // { data: [...] }
// Remove our strong reference
expensiveObject = null;
// After garbage collection runs, deref() returns undefined
setTimeout(() => {
console.log(cache.get('result')); // undefined (object was collected)
}, 100);
The key method is deref()—it returns the wrapped object if it's still alive, or undefined if it's been collected. WeakRef works with any value (objects, primitives, functions), making it more flexible than WeakMap.
In production, I've used WeakRef for image caches in a React Native travel app. As users scroll and old images go out of view, the image objects are garbage-collected, freeing memory. The cache still holds WeakRefs to them, but deref() safely returns undefined when they're gone. No manual eviction logic needed.
Don't Rely on Collection Timing
This is the critical mindset shift. WeakMap and WeakRef do not guarantee when an object will be collected—only that they don't prevent it. The garbage collector runs at unpredictable intervals. On some JavaScript engines it's immediate; on others it may take seconds or longer. You cannot use them for deterministic cleanup or resource management.
// DON'T do this:
const registry = new WeakMap();
class File {
handle: any;
constructor() {
this.handle = Math.random(); // pretend this is an OS file handle
registry.set(this, { closed: false });
}
}
let file = new File();
file = null;
// BAD: You can't assume the file is closed just because you nulled the reference.
// GC might not run for seconds, so the file handle leaks.
Instead, use WeakMap and WeakRef for scenarios where eventual cleanup is acceptable: caches, metadata, and bookkeeping. For resources that must be cleaned up (file handles, database connections, timers), use explicit cleanup patterns: try-finally, try-with-resources equivalents, or manual dispose() methods.
// DO this:
class File {
handle: any;
closed = false;
constructor() {
this.handle = Math.random();
}
close() {
if (!this.closed) {
// Deterministic cleanup
this.closed = true;
console.log('File handle closed');
}
}
}
// Use WeakMap to track which files have been closed, for debugging
const debugInfo = new WeakMap();
let file = new File();
debugInfo.set(file, { openTime: Date.now() });
file.close(); // Explicit cleanup
file = null;
Common Mistakes
- Storing primitives in WeakMap: WeakMap keys must be objects. If you try to use a string or number, you'll get a TypeError. Use a regular Map if you need primitive keys.
- Assuming predictable cleanup: Don't write code that depends on knowing when a WeakMap entry vanishes. Treat collection as eventual, not immediate.
- Iterating or checking size: WeakMap has no iteration methods or size property. If you need to know what's stored, use a regular Map. WeakMap is deliberately opaque.
- Using WeakRef for resource management: File handles, connections, and timers need explicit cleanup. WeakRef is for caches and soft references, not critical resources.
- Overcomplicating with WeakMap when a closure or private field works: Sometimes a simple closure or a private
#fieldis clearer than WeakMap. Use WeakMap when you actually need to key by external objects.
Key Takeaways
- WeakMap holds key-value pairs where keys can be garbage-collected without keeping the WeakMap entry alive.
- Use WeakMap to attach metadata to objects safely, avoiding memory leaks from holding references to external objects.
- WeakRef wraps a value so it doesn't prevent garbage collection; useful for optional caches that degrade gracefully when memory is scarce.
- Never rely on WeakMap or WeakRef for deterministic cleanup or resource management—garbage collection timing is unpredictable.
- Treat WeakMap and WeakRef as performance and memory-leak-prevention tools, not as cleanup mechanisms.
- WeakMap keys must be objects; WeakRef works with any value type.






Comments
Loading comments…