How JavaScript Handles Memory: Strong vs. Weak References
Memory and performance management are essential parts of software development, and JavaScript developers need to understand how references affect what the garbage collector can reclaim. While strong references are the default in JavaScript, the language also offers WeakMap, WeakSet, and WeakRef for cases where you do not want to keep an object alive.
Strong References Keep Objects Alive
A strong reference is one that prevents an object from being garbage-collected. As long as a strong reference exists, the object stays in memory. Consider storing an object in an array:
let man = {name: "Joe Doe"};
let human = [man];
man = null;
console.log(human);
The object remains accessible through the array even if the original variable is overwritten:
console.log(human[0])
This is the default behavior in JavaScript: references are strong unless you explicitly use a weak collection.
Weak References Allow Cleanup
A weak reference does not prevent the garbage collector from reclaiming an object. Even if the weak reference is the only remaining reference, the object can still be collected. For example, if you store an object in a WeakMap and then set the original variable to null, the only reference to the object is weak.
// Create an instance of the WeakMap object.
let human = new WeakMap():
// Create an object, and assign it to a variable called man.
let man = { name: "Joe Doe" };
// Call the set method on human, and pass two arguments (key and value) to it.
human.set(man, "done")
console.log(human)
When the garbage collector runs, the object is removed from memory and from the WeakMap. This automatic cleanup is the key difference from strong references.
Reachability and Garbage Collection
JavaScript automatically allocates memory when objects are created and frees it when they are no longer needed. The process of freeing memory is garbage collection, and it is based on the idea of reachability. Values are considered reachable if they are:
- values in the root of the program or referenced from the root, such as global variables or the currently executing function, its context, and callback;
- values accessible from the root by a reference or chain of references.
Reachable values are kept in memory. If an object can be reached through a chain of references, it survives. If the last reference is removed, the object becomes unreachable and is collected.
let languages = {name: “JavaScript”};
If you reassign the languages variable to null, the object becomes unreachable and is garbage-collected:
languages = null;
However, if another variable still references the object, it remains:
languages = null;
Set vs. WeakSet
A Set is a collection of unique values. You can iterate over it with for… of or .forEach. The elements in a Set are strongly held, so they stay in memory as long as the set exists.
A WeakSet differs in three important ways:
- It may only contain objects.
- It cannot be looped through.
- It uses weak references, so objects can be garbage-collected if there are no other references to them.
const human = new WeakSet();
let paul = {name: "Paul"};
let mary = {gender: "Mary"};
// Add the human with the name paul to the classroom.
const classroom = human.add(paul);
console.log(classroom.has(paul)); // true
paul = null;
// The classroom will be cleaned automatically of the human paul.
console.log(classroom.has(paul)); // false
When you set a referenced object to null, the WeakSet is automatically cleaned, and the object is removed from memory. This is not the case with a Set.
Map vs. WeakMap
A Map holds key-value pairs and remembers the original insertion order of the keys. Values in a Map are strongly referenced. As long as the map exists, the values stored in it will not be garbage-collected, even if there are no other references to them.
WeakMap behaves similarly to Map, with two major exceptions: the keys must be objects, and the map holds weak references to those keys. Because the references are weak, objects used as keys can be garbage-collected if they are not referenced elsewhere. WeakMap is not enumerable, and you access values through the .get() method.
// Create a weakMap.
let weakMap = new WeakMap();
let weakMap2 = new WeakMap();
// Create an object.
let ob = {};
// Use the set method.
weakMap.set(ob, "Done");
// You can set the value to be an object or even a function.
weakMap.set(ob, ob)
// You can set the value to undefined.
weakMap.set(ob, undefined);
// WeakMap can also be the value and the key.
weakMap.set(weakMap2, weakMap)
// To get values, use the get method.
weakMap.get(ob) // Done
// Use the has method.
weakMap.has(ob) // true
weakMap.delete(ob)
weakMap.has(ob) // false
The main side effect of using objects as keys in a WeakMap is that entries are automatically removed from memory when the key object becomes unreachable.
Practical Uses for WeakMap
WeakMap is useful in two common areas: caching and storing additional data about objects.
Caching Function Results
Caching stores a copy of a computed result so it does not need to be recalculated. If you use a Map for caching, you must manually clean up entries when objects are no longer needed. With a WeakMap, the cached result is automatically removed when the key object is garbage-collected:
let cachedResult = new WeakMap();
// A function that stores a result.
function keep(obj){
if(!cachedResult.has(obj){
let result = obj;
cachedResult.set(obj, result);
}
return cachedResult.get(obj);
}
let obj = {name: "Frank"};
let resultSaved = keep(obj)
obj = null;
// console.log(cachedResult.size); Possible with map, not with WeakMap
This saves memory and avoids manual cache invalidation. Caching can improve performance by reducing database calls, API requests, and server-to-server communication.
Storing Additional Data on Objects
Another common use for WeakMap is attaching auxiliary data to objects without modifying them. Consider a visitor-counting program where you need to decrement the count when a visitor leaves:
let visitorCount = new WeakMap();
function countCustomer(customer){
let count = visitorCount.get(customer) || 0;
visitorCount.set(customer, count + 1);
}
In client code, you would update the count when visitors arrive and leave:
let person = {name: "Frank"};
// Taking count of person visit.
countCustomer(person)
// Person leaves.
person = null;
With a Map, the visitorCount data would need to be cleaned manually when a visitor leaves; otherwise, the map grows indefinitely. With a WeakMap, the data is garbage-collected automatically as soon as the visitor object becomes unreachable, eliminating the need for manual cleanup.



