JavaScript WeakMap and WeakSet: Use Cases and Limitations
Learn what WeakMap and WeakSet are in JavaScript, how they relate to garbage collection, their limitations, and practical uses like private data and caching.
What you will learn
In the last lesson you learned Map and Set. JavaScript also has two special cousins: WeakMap and WeakSet. They look almost the same, but they have a superpower: they hold their objects weakly, which means they do not stop the JavaScript engine from cleaning an object out of memory when nobody else is using it. In this lesson you will learn what “weak” really means, how garbage collection fits in, how to use both collections, their limitations, and the situations where they are the right tool.
First, a little about memory
Every object you create takes up memory. JavaScript has a built-in cleaner called the garbage collector. Its rule is simple:
If an object can still be reached from your code (some variable or property still points to it), keep it. If nothing points to it any more, delete it and free the memory.
let user = { name: "Riya" }; // the object is reachable through `user`
user = null; // nothing points to the object now
// The garbage collector can remove it from memory
The problem with a normal Map
A normal Map or Set holds a strong reference to its keys. That means the object is kept alive as long as the Map exists, even if you do not need it any more:
let user = { name: "Riya" };
const visits = new Map();
visits.set(user, 5);
user = null; // we are done with the user
console.log(visits.size); // 1 (the Map still holds the object, so it is NOT cleaned up)
If your program keeps adding objects to a Map and never removes them, memory grows and grows. This is called a memory leak.
The solution: weak references
A WeakMap or WeakSet holds its keys weakly. If the only thing still pointing to an object is a WeakMap or WeakSet, the garbage collector is free to delete the object, and the entry disappears automatically.
let user = { name: "Riya" };
const visits = new WeakMap();
visits.set(user, 5);
user = null; // no other reference exists
// Later, the garbage collector can remove the object AND its entry from the WeakMap.
You do not have to clean anything yourself. The data lives exactly as long as the object it belongs to.
Think of it like this: a
Mapis a notice board with strong glue: whatever you stick there stays forever. AWeakMapis a note that falls off the moment the thing it describes is thrown away.
WeakMap
A WeakMap stores key-value pairs, but with strict rules:
- Keys must be objects (arrays and functions count as objects too). Primitives like text and numbers are not allowed.
- Values can be anything.
The methods
A WeakMap has only four methods:
| Method | What it does |
|---|---|
set(key, value) |
Adds or updates an entry |
get(key) |
Returns the value, or undefined |
has(key) |
Returns true or false |
delete(key) |
Removes the entry |
const info = new WeakMap();
const user = { name: "Riya" };
const product = { title: "Pen" };
info.set(user, { visits: 3 });
info.set(product, "in stock");
console.log(info.get(user)); // { visits: 3 }
console.log(info.has(product)); // true
info.delete(product);
console.log(info.has(product)); // false
Keys must be objects
const weak = new WeakMap();
try {
weak.set("name", "Riya"); // text is not allowed
} catch (error) {
console.log(error.name); // "TypeError"
console.log(error.message); // Invalid value used as weak map key
}
const key = {};
weak.set(key, "works"); // OK
Why? Text and numbers are values, not objects in memory. There is nothing for the garbage collector to clean up, so they cannot be weak keys.
WeakSet
A WeakSet is a set that holds objects only, weakly.
| Method | What it does |
|---|---|
add(object) |
Adds an object |
has(object) |
Returns true or false |
delete(object) |
Removes an object |
const clicked = new WeakSet();
const buttonA = { id: "save" };
const buttonB = { id: "cancel" };
clicked.add(buttonA);
console.log(clicked.has(buttonA)); // true
console.log(clicked.has(buttonB)); // false
clicked.delete(buttonA);
console.log(clicked.has(buttonA)); // false
A WeakSet is for answering one question: “Have I seen or marked this object?” It does not store data, only presence.
Limitations
Because the garbage collector can remove entries at any time (you cannot know when), JavaScript hides everything that would show the changing contents. So both WeakMap and WeakSet have these limits:
| You cannot… | Because |
|---|---|
Use primitives as keys or values (WeakSet) |
Only objects can be garbage collected |
Check .size |
The count could change at any moment |
Loop with for...of or forEach |
The list of entries is not stable |
Use keys(), values() or entries() |
Same reason |
Use clear() |
Not available (just drop the whole WeakMap and make a new one) |
| Convert to an array or JSON | They cannot be listed |
const weak = new WeakMap();
const key = {};
weak.set(key, 1);
console.log(weak.size); // undefined
// for (const x of weak) {} // TypeError: weak is not iterable
// weak.clear(); // TypeError: weak.clear is not a function
Also keep in mind:
- You cannot see the garbage collector working. It runs when the engine decides, so you cannot write code that depends on an entry disappearing at a certain time.
- A key must be the same object reference to find the value, just like in a normal Map.
Map vs WeakMap, Set vs WeakSet
Map / Set |
WeakMap / WeakSet |
|
|---|---|---|
| Keys / values | Any type | Objects only |
| Reference to objects | Strong (keeps them alive) | Weak (does not keep them alive) |
| Memory cleanup | You must delete entries | Automatic |
size |
Yes | No |
| Looping | Yes | No |
clear() |
Yes | No |
| Use when | You need to list, count or loop | You only attach data to objects or mark objects |
Rule of thumb: if you need to look at all the entries, use
MaporSet. If you only need to attach something to an object and want it to disappear with the object, useWeakMaporWeakSet.
Real-life use cases
1. Private data for objects
Store hidden information about an object outside of it, so nobody can read it from outside:
const secrets = new WeakMap();
class User {
constructor(name, password) {
this.name = name;
secrets.set(this, { password }); // hidden from outside
}
checkPassword(attempt) {
return secrets.get(this).password === attempt;
}
}
const riya = new User("Riya", "abc123");
console.log(riya.name); // "Riya"
console.log(riya.password); // undefined (not visible)
console.log(riya.checkPassword("abc123")); // true
When riya is no longer used, its secret data disappears with it. (Modern classes can also use #private fields, which you will see later. WeakMap is the older, flexible way, and it still works with plain objects.)
2. Caching results for objects
Remember an expensive result per object, without causing a memory leak:
const cache = new WeakMap();
function heavyCalculation(data) {
if (cache.has(data)) {
console.log("from cache");
return cache.get(data);
}
const result = data.numbers.reduce((sum, n) => sum + n, 0); // pretend this is slow
cache.set(data, result);
return result;
}
const report = { numbers: [10, 20, 30] };
console.log(heavyCalculation(report)); // 60
console.log(heavyCalculation(report)); // "from cache", then 60
With a normal Map, every report ever processed would stay in memory forever. With WeakMap, the cache entry is released when the report object is no longer used.
3. Attach extra data to DOM elements
const clickCounts = new WeakMap();
function trackClick(element) {
const count = clickCounts.get(element) ?? 0;
clickCounts.set(element, count + 1);
}
const button = document.createElement("button");
trackClick(button);
trackClick(button);
console.log(clickCounts.get(button)); // 2
If the button is removed from the page and no code points to it, both the button and its click count can be cleaned up automatically.
4. Mark objects as “already processed” (WeakSet)
const processed = new WeakSet();
function process(item) {
if (processed.has(item)) {
console.log("already done");
return;
}
processed.add(item);
console.log("processing", item.id);
}
const order = { id: 101 };
process(order); // "processing 101"
process(order); // "already done"
5. Protect against processing a circular structure twice
When walking through nested objects that may point back to each other, a WeakSet remembers what you have already visited:
function countObjects(value, seen = new WeakSet()) {
if (typeof value !== "object" || value === null) return 0;
if (seen.has(value)) return 0; // already counted, avoid an endless loop
seen.add(value);
let total = 1;
for (const key in value) {
total += countObjects(value[key], seen);
}
return total;
}
const a = { name: "A" };
const b = { name: "B", friend: a };
a.friend = b; // a circular link: a -> b -> a
console.log(countObjects(a)); // 2
6. Allow an action only once per object
const alreadyWelcomed = new WeakSet();
function welcome(user) {
if (alreadyWelcomed.has(user)) return "Welcome back!";
alreadyWelcomed.add(user);
return `Hello, ${user.name}!`;
}
const riya = { name: "Riya" };
console.log(welcome(riya)); // "Hello, Riya!"
console.log(welcome(riya)); // "Welcome back!"
7. Check that a method is called on a valid object
const validObjects = new WeakSet();
class Token {
constructor() {
validObjects.add(this);
}
}
function use(token) {
if (!validObjects.has(token)) {
throw new Error("Not a real Token");
}
return "ok";
}
console.log(use(new Token())); // "ok"
// use({}); // Error: Not a real Token
When NOT to use them
- When you need the number of entries, or need to loop over them: use
MaporSet. - When your keys are text or numbers (like ids or names): use
Map. - When you must keep the data even after the object is gone: use
Map. - When you want to save the data (JSON, localStorage): they cannot be listed, so use a normal structure.
For most everyday code, a normal Map or Set is enough. WeakMap and WeakSet are tools for special situations: attaching data to objects you do not own, caches, and private data.
Common mistakes
- Using text or numbers as keys.
weak.set("id", 1)throws a TypeError. Keys must be objects. - Expecting
size,forEach,for...oforclear(). They do not exist on weak collections. - Expecting an entry to disappear instantly after
obj = null. The garbage collector decides when. You cannot rely on the exact time. - Using a new but equal object to look something up.
weak.get({ id: 1 })will not find the entry stored with a different{ id: 1 }object. - Using a WeakMap when you need to list all the data. You cannot list it. Use
Map. - Thinking WeakMap makes values private from everyone. It is only private if the WeakMap itself is not exposed to other code.
- Keeping another reference by accident. If your code stores the object elsewhere (in an array, for example), it stays alive and the weak entry stays too.
Practice
- Create a
WeakMap, use a user object as the key and{ visits: 1 }as the value. Read it back withgetand check it withhas. - Try to use the text
"name"as a WeakMap key inside atry...catch. Print the error name. - Try
weak.sizeandweak.clear()on a WeakMap. Explain what you see and why. - Write a function
markSeen(obj)that uses aWeakSetand prints"new"the first time and"seen"after that. - Create a
WeakMapcache for a function that receives an object and returns an expensive result. Check that the second call comes from the cache. - Create a class
Accountthat keeps itsbalancehidden in aWeakMap, withdepositandgetBalancemethods. - Explain in your own words, in two sentences, the difference between a
Mapand aWeakMapand when to choose each. - Challenge: write a function
once(fn)that stores which objects were already passed tofnusing aWeakSet, and ignores them the next time.
Recap
- JavaScript’s garbage collector removes objects that can no longer be reached. A normal
MaporSetkeeps its objects alive (strong references), which can cause memory leaks. WeakMapandWeakSethold objects weakly. They do not stop an object from being cleaned up, and the entry disappears with the object.WeakMapmethods:set,get,has,delete.WeakSetmethods:add,has,delete.- Keys (WeakMap) and values (WeakSet) must be objects. Primitives throw a TypeError.
- Limitations: no
size, no looping, nokeys()orvalues(), noclear(), and you cannot predict when cleanup happens. - Use them to attach data to objects (private data, caches, DOM element data) and to mark objects (visited, processed, valid) without causing memory leaks.
- Use
MapandSetwhen you need to count, list or loop, or when your keys are not objects. - Next you will learn JSON: how to turn objects into text and back, which is how data travels across the web.