JavaScriptAdvanced

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.

All JavaScript lessons

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 Map is a notice board with strong glue: whatever you stick there stays forever. A WeakMap is 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 Map or Set. If you only need to attach something to an object and want it to disappear with the object, use WeakMap or WeakSet.

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 Map or Set.
  • 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...of or clear(). 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

  1. Create a WeakMap, use a user object as the key and { visits: 1 } as the value. Read it back with get and check it with has.
  2. Try to use the text "name" as a WeakMap key inside a try...catch. Print the error name.
  3. Try weak.size and weak.clear() on a WeakMap. Explain what you see and why.
  4. Write a function markSeen(obj) that uses a WeakSet and prints "new" the first time and "seen" after that.
  5. Create a WeakMap cache for a function that receives an object and returns an expensive result. Check that the second call comes from the cache.
  6. Create a class Account that keeps its balance hidden in a WeakMap, with deposit and getBalance methods.
  7. Explain in your own words, in two sentences, the difference between a Map and a WeakMap and when to choose each.
  8. Challenge: write a function once(fn) that stores which objects were already passed to fn using a WeakSet, and ignores them the next time.

Recap

  • JavaScript’s garbage collector removes objects that can no longer be reached. A normal Map or Set keeps its objects alive (strong references), which can cause memory leaks.
  • WeakMap and WeakSet hold objects weakly. They do not stop an object from being cleaned up, and the entry disappears with the object.
  • WeakMap methods: set, get, has, delete. WeakSet methods: add, has, delete.
  • Keys (WeakMap) and values (WeakSet) must be objects. Primitives throw a TypeError.
  • Limitations: no size, no looping, no keys() or values(), no clear(), 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 Map and Set when 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.