completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

WeakMap & WeakSet

Attach data to objects without keeping them alive: use WeakMap for per-object caches and private data, WeakSet for tracking objects, and non-registered symbols as modern weak keys.

By the end, you can
  • 01
    Explain what weak meansSay why a WeakMap pair does not keep its key object alive.
  • 02
    Use the WeakMap and WeakSet methodsUse set, get, has, and delete with keys that are allowed, and know which keys throw a TypeError.
  • 03
    Choose the right collectionPick Map, WeakMap, Set, or WeakSet for a cache, notes on an object, or marking objects you have seen.

Notes that belong to an object

You have already met Map. A Map stores pairs: each key leads to one value. You can count its pairs, loop over them, and keep them as long as you like.

A WeakMap also stores pairs, but each key must be an object. It is made for one job: attach extra data to an object without keeping that object alive.

What does "keeping an object alive" mean? JavaScript frees the memory of objects that your code can no longer get to. This cleanup is called garbage collection. A normal Map is one way to get to its keys, so a Map stops its keys from being cleaned up. A WeakMap does not.

You will use this idea in three ways in this lesson:

  • Saving a result for each object, so you don't work it out twice. This is called a cache.
  • Keeping private data for each object.
  • Marking objects you have already handled, with a WeakSet.
Plain definition

A WeakMap stores values by key. Each key must be an object, or a symbol made with Symbol(). The WeakMap holds its keys weakly: if nothing else in your code can reach a key, its pair may disappear. A WeakSet remembers which objects it holds, and holds them weakly in the same way. Only the link to the key is weak. A stored value is held like any normal value.

Real-life analogyA name tag that leaves with the guest

Picture a party. Each guest wears a name tag with a note on it. A guest list keeps every name, even after a guest goes home. A name tag is different: when the guest leaves, the tag leaves too. A Map is like the guest list. A WeakMap is like the name tags.

In real life: A guest wearing a name tag
In JavaScript: An object used as a key
In real life: A guest list keeps every name, even after guests go home
In JavaScript: A Map keeps its keys
In real life: The name tag leaves with the guest
In JavaScript: A WeakMap pair can go when its key object goes
In real life: Nobody can read a list of old name tags
In JavaScript: A WeakMap has no size, no keys(), and no loop

This lesson uses ideas from Map and Set, Symbols, Objects & references, and Closures. Two later lessons, Memory management & garbage collection and Weak references & ephemerons, go deeper. Here you need one word. An object is reachable when your code can still get to it through a variable or through another object.

Keys a WeakMap does not keep

STEP THROUGH

The "weak" in WeakMap does not mean small or slow. It means the WeakMap's link to each key is weak. That link alone does not keep the key alive.

While your code can still reach the key in some other way, a WeakMap works like a Map. get, has, set, and delete all work as usual. The difference only shows when the rest of your code lets go of the key.

Step through one object used as a key in both a Map and a WeakMap:

One object as a key in a Map and a WeakMap
Step 0 of 7Ready
Your turn: follow the blue line

Step through one object used as a key in a Map and in a WeakMap. While the object is still reachable, get gives the same kind of answer in both.

Running in
  1. script
Next: line 1
const strong = new Map();const weak = new WeakMap(); strong.set(profile, "kept by Map");weak.set(profile, "attached weakly"); console.log(strong.get(profile));console.log(weak.get(profile));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

The replay never shows a pair disappearing. That is on purpose. Your code can't see when garbage collection runs. The engine, the program that runs your JavaScript (like the one inside Chrome), picks the time and keeps it private. This lesson can show you the rules, and a model of which objects are reachable. It can't show the engine's real memory.

Two objects that look the same are two keysPop out in the code editor (opens in a new tab)JavaScript
const firstUser = { id: 7 };const secondUser = { id: 7 };const notes = new WeakMap(); notes.set(firstUser, "first");console.log(notes.get(firstUser));console.log(notes.has(secondUser));

Lines 1 and 2 make two different objects. Both happen to hold id: 7. Line 5 stores a note for firstUser, and line 6 prints first. Line 7 asks about secondUser and prints false.

Two objects that look the same. The tag firstUser points to an object with id 7, and that object has the note "first" in the WeakMap. The tag secondUser points to a different object with id 7. It has no note.keyvaluefirstUser{ id: 7 }"first"secondUser{ id: 7 }no note
Both objects hold id: 7, but they are two objects. The note was stored for firstUser, so only that object has it.

A WeakMap finds a key by which object it is, not by what the object holds. Two objects with the same contents are still two different keys.

The methods and the key rules

REAL CODE

A WeakMap has only four methods:

  • set(key, value) stores a value for a key.
  • get(key) reads the value back.
  • has(key) answers true or false: is there a pair for this key?
  • delete(key) removes the pair.

A WeakSet has three, the same ones a Set uses to add and check items: add, has, and delete.

The rule for keys is strict:

  • A WeakMap key must be an object, or a symbol made with Symbol().
  • A WeakSet item follows the same rule.
  • Anything else, like a string or a number, throws a TypeError.
Left, can be a key: an object, an array, and Symbol("x"). Right, throws TypeError: the string "id", the number 42, and Symbol.for("x").can be a key{ id: 1 }[1, 2]Symbol("x")TypeError"id"42Symbol.for("x")
Objects (arrays too) and symbols made with Symbol() can be WeakMap keys and WeakSet items. Strings, numbers, and symbols from Symbol.for() throw a TypeError.
Only the link to the key is weak

A WeakMap holds its stored values in the normal way. If a value points to another object, that object stays reachable through the value. Only the WeakMap's link to the key is weak.

A stored value is held in the normal wayPop out in the code editor (opens in a new tab)JavaScript
const metadata = new WeakMap();const user = {};const settings = { theme: "light" }; metadata.set(user, settings);console.log(metadata.get(user).theme);

Line 3 makes a settings object. Line 5 stores it as the value for user. Line 6 prints light. The value is held normally, so it is still there.

Now run all the rules in one program. Watch for two errors at the end:

Run the WeakMap and WeakSet rules
Step 0 of 15Ready
Your turn: follow the blue line

Run the rules in real code. The object key and the Symbol() key work. Then come two TypeErrors.

Running in
  1. script
Next: line 1
const weakSet = new WeakSet();const obj = {};const localSymbol = Symbol("token");const registered = Symbol.for("token"); weakMap.set(obj, "object ok");weakSet.add(obj);weakMap.set(localSymbol, "symbol ok"); console.log(weakMap.get(obj));console.log(weakSet.has(obj));console.log(weakMap.get(localSymbol));console.log(typeof weakMap.size);console.log(typeof weakMap.keys); try { weakMap.set("id", 1); } catch (error) { console.log(error.name); }try { weakMap.set(registered, 1); } catch (error) { console.log(error.name); }
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
delete tells you if there was a pair to removePop out in the code editor (opens in a new tab)JavaScript
const notes = new WeakMap();const user = {}; notes.set(user, "saved");console.log(notes.delete(user));console.log(notes.has(user));console.log(notes.delete(user));

Line 5 removes the pair and prints true. Line 6 checks the same object and prints false: the pair is gone. Line 7 tries again. There is nothing left to remove, so it prints false.

Here your own code removed the pair, so you know exactly when it happened. Garbage collection is different: the engine picks the time, and your code can't see it.

Asking about a missing key is not an errorPop out in the code editor (opens in a new tab)JavaScript
const notes = new WeakMap();const user = {}; console.log(notes.get(user));console.log(notes.has(user));

Asking about a key that has no pair is safe. Line 4 prints undefined. Line 5 prints false.

has tells a stored undefined from a missing pairPop out in the code editor (opens in a new tab)JavaScript
const notes = new WeakMap();const user = {}; notes.set(user, undefined);console.log(notes.get(user));console.log(notes.has(user));

Sometimes the stored value is undefined on purpose. Line 4 stores it. Line 5 prints undefined, the same as for a missing key. So get alone can't tell you if the pair exists. Line 6 prints true: has answers that question.

The table lists each weak collection: what it accepts, which methods it has, and which Map or Set features it leaves out.

What WeakMap and WeakSet accept, and which methods they have
CollectionWhat it acceptsMethods it hasWhat it does not have
WeakMapObjects, and symbols made with Symbol(), as keys.set, get, has, deleteNo size, keys, values, entries, or forEach.
WeakSetObjects, and symbols made with Symbol(), as items.add, has, deleteNo size, no loop, and no forEach.
Why is there no list?

Imagine a WeakMap had a size. You could check it again and again. The moment it dropped, you would know the engine had just cleaned up an object. JavaScript keeps that timing private. So WeakMap and WeakSet have no size, and you can't loop over them.

When an object can be cleaned up

INTERACTIVE

Think of links as arrows. Many things can point to an object:

  • a variable
  • a property of another object
  • an item in an array
  • a function that remembers it (a closure)
  • a key in a normal Map

If your running code can follow some chain of these arrows to an object, the object is reachable. When no chain is left, the engine is allowed to clean the object up. A WeakMap's arrow to its key doesn't count.

Both after user = null. Left: a Map cart still holds the object { name: "Ada" } as a key, so Ada stays. Right: with a WeakMap, nothing holds the object, so Ada can be cleaned up.Map, after user = null{ name: "Ada" }mapAda staysWeakMap, after user = null{ name: "Ada" }nothing holds itAda can be cleaned up
After user = null, a Map still holds the object as a key, so the object stays. A WeakMap's link does not count, so nothing holds the object. The engine may clean it up later.

Try it. Pick Map or WeakMap, then run user = null and see which arrows are left:

Which arrows keep the object?
WeakMap versionPop out in the code editor (opens in a new tab)JavaScript
let user = { name: "Ada" };const notes = new WeakMap();notes.set(user, "cached stats"); user = null; // drop the last variable reference
Which arrows are leftweakmap
variable user→ the object
object { name: "Ada" }your code can reach it
weakmapits arrow does not count
pairworks while the key can be reached
Try it yourself

The variable user points to the object, so a lookup works in a Map or a WeakMap. This is a model of the arrows. It does not look at real memory.

A model of the arrows between things. It shows which arrows are left. It can't see real garbage collection, and it can't say when cleanup will happen.

The model is drawn with cards. It does not look at real memory. "Can be cleaned up" means the engine may free the object at some later time. It does not mean that has happened yet, and your code gets no signal when it does.

Saving a result for each object

STEP THROUGH

A cache saves the result of slow work, so the next call can reuse it. When the input is an object, a WeakMap makes a good cache:

  • The saved result belongs to that exact object.
  • The cache does not keep old objects alive forever.

Step through a cache that saves stats for a user object:

A cache: the first call works, the second reuses
Step 0 of 13Ready
Your turn: follow the blue line

Step through a cache that saves a result for each object. The first call does the work. The second call reuses it. The cache never keeps the user alive by itself.

Running in
  1. script
Next: line 1
let calculations = 0; function userStats(user) {  if (statsCache.has(user)) return statsCache.get(user);  calculations = calculations + 1;  const stats = { label: user.name, score: user.visits * 10 };  statsCache.set(user, stats);  return stats;} const ada = { name: "Ada", visits: 3 };console.log(userStats(ada).score);console.log(userStats(ada).score);console.log(calculations);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

You will find this pattern in real code, for example when you save:

  • totals worked out from user records
  • facts about the parts of a code tree
  • the measured size of elements on a page

While some part of your app still uses the object, the cache works. Once nothing uses the object, its cache pair can be cleaned up with it.

Map or WeakMap?
  • You need to list every key later
  • Save measured sizes for page elements that other code created
  • Count how often each word appears
  • Attach check results to user objects from another library
  • Show how many players are in the table right now
  • Store hidden data for class objects in older code
Try it yourself
0 of 6 correct

Pick Map when you need to loop, count, or use strings and numbers as keys. Pick WeakMap for extra data that should last only as long as an object does.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Private data

COMPARE

A class can keep its data in a WeakMap instead of on the object. Before JavaScript had #private fields, many libraries did this:

  • Put one WeakMap outside the class.
  • Use each new object, this, as a key.
  • Store the hidden data as the value.

Code outside can't read the WeakMap unless it can reach the variable that holds it.

Hide a counter's data in a WeakMapPop out in the code editor (opens in a new tab)JavaScript
const privateData = new WeakMap(); class Counter {  constructor(name) {    privateData.set(this, { name, count: 0 });  }  click() {    const data = privateData.get(this);    data.count = data.count + 1;    return data.name + ": " + data.count;  }} const counter = new Counter("save");console.log(counter.click());console.log(counter.count);

Line 1 makes the WeakMap. Line 5 stores { name, count: 0 } with the new object as the key. Lines 8 and 9 get that data back and add 1 to the count. Line 15 prints save: 1. Line 16 prints undefined, because count is not a property of counter. It lives in the WeakMap.

The table compares three ways to keep data private. Each row says what the way is good at and what to watch for.

Three ways to keep data private
Way to hide dataWhat it is good atWhat to watch for
A closureA function returns other functions, and they remember hidden variables.Great for functions. Each object may get its own copy of every method.
A WeakMapObjects made from a class, in code written before #private fields.You need a WeakMap outside the class, and you must use this with care.
#private fieldsModern class code. The language itself blocks access from outside.They only work inside a class. The Private fields & methods lesson covers them.

Compare this with the Closures lesson. A closure hides variables because a function remembers the variables around it. The WeakMap way hides a lookup table instead. In modern classes, #private fields are usually easier to read.

Notes on objects you do not own

PRACTICAL

Sometimes a library hands you an object. You should not add your own fields to it, because the library owns it. A WeakMap lets you keep your notes beside the object, without changing the object.

Keep a form check beside the formPop out in the code editor (opens in a new tab)JavaScript
const validation = new WeakMap();const form = { email: "asha@example.com" }; validation.set(form, { valid: true });console.log(validation.get(form).valid);console.log(form.valid);

Line 2 is a form object from another part of the app. Line 4 stores a check result for it. Line 5 reads the result from the WeakMap and prints true. Line 6 prints undefined, because the form itself did not change. When the rest of the app lets go of the form, the form can still be cleaned up.

First ask who owns the data

Use a WeakMap for notes or a cache that should last only as long as an object. Use a normal Map when your code owns the data, needs to count it, or must list it in a report. A WeakMap does not replace every Map.

WeakSet: marking objects you have seen

INTERACTIVE

A WeakSet answers one question: "Have I seen this object?" It stores no values. Use it for objects you handle once, like:

  • widgets on a page that you have already set up
  • points you have already visited while walking through a tree or a graph
  • page elements that some other code created

A WeakSet holds its objects weakly, so it is never the reason an object stays alive. To pick the right tool:

  • Need a saved result for each object? Use a WeakMap.
  • Need to list or count the items? Use a normal Set.
A WeakSet does not take stringsPop out in the code editor (opens in a new tab)JavaScript
const seen = new WeakSet();const card = {}; seen.add(card);console.log(seen.has(card)); try { seen.add("card"); }catch (error) { console.log(error.name); }

Line 4 adds the object from line 2, and line 5 prints true. Line 7 tries to add the string "card". A string is not an object, so it throws, and line 8 prints TypeError. A WeakSet follows the same rule as WeakMap keys: objects, and symbols made with Symbol().

delete takes an object out of a WeakSetPop out in the code editor (opens in a new tab)JavaScript
const seen = new WeakSet();const card = {}; seen.add(card);console.log(seen.delete(card));console.log(seen.has(card));

Line 5 removes the card and prints true. Line 6 prints false. So you can take an object out of a WeakSet yourself, even though you can't list what is inside.

Two todo objects are two itemsPop out in the code editor (opens in a new tab)JavaScript
const processed = new WeakSet();const firstTodo = {};const secondTodo = {}; processed.add(firstTodo);console.log(processed.has(firstTodo));console.log(processed.has(secondTodo));

Lines 2 and 3 make two separate todo objects. Line 5 adds only the first one. Line 6 prints true for it. Line 7 prints false for the second. Like a WeakMap, a WeakSet checks which object it is, not what the object holds.

Real-life analogyA stamp on a guest's hand

At a concert, staff stamp your hand when you come in. If you step out and come back, they check the stamp and let you through. When you go home, the stamp goes home with you. A WeakSet marks objects in the same way.

In real life: The guest
In JavaScript: An object
In real life: The stamp on the hand
In JavaScript: Being in the WeakSet
In real life: The door staff check for the stamp
In JavaScript: weakSet.has(object)
Mark an object as done
Skip an object you have already set upPop out in the code editor (opens in a new tab)JavaScript
const processed = new WeakSet(); function initialize(card) {  if (processed.has(card)) return "already initialized";  processed.add(card);  return "initialized now";} const card = { id: "signup" };initialize(card);initialize(card);
Result0 calls

Not run yet

The WeakSet can answer has(card), but it can't list the cards it holds.

Try it yourself

Press the button. The first call will add the object to the WeakSet.

A WeakSet is a good fit when you only need to know if you have seen an object, and you never need to list them all.
Left, first call: card is not in the processed WeakSet yet, so it goes in and the call returns "initialized now". Right, second call: card is already in processed, so the call returns "already initialized".1st call: not seen yetcardcardprocessed"initialized now"2nd call: already incardprocessedcard"already initialized"
The first call finds no card in the WeakSet, so it adds it and does the work. The second call finds it already there and skips the work.

Symbols as WeakMap keys

ES2023

Since 2023, JavaScript also lets you use some symbols as WeakMap keys. A symbol is a unique value. There are two ways to make one:

  • Symbol("local") makes a brand-new symbol. When no code holds it any more, it can be cleaned up. So it can be a WeakMap key.
  • Symbol.for("name") gets a symbol from one shared list for the whole program, called the global symbol registry. That list keeps the symbol forever. So this kind, called a registered symbol, can't be a WeakMap key.
A Symbol() key works, a Symbol.for() key throwsPop out in the code editor (opens in a new tab)JavaScript
const local = Symbol("local");const shared = Symbol.for("shared");const metadata = new WeakMap(); metadata.set(local, "ok");console.log(metadata.get(local)); try { metadata.set(shared, "nope"); }catch (error) { console.log(error.name); }

Line 1 makes a local symbol. Line 5 uses it as a key, and line 6 prints ok. Line 2 makes a registered symbol. Line 8 tries to use it as a key. That throws, and line 9 prints TypeError. The Symbols lesson explains both kinds of symbol in more detail.

Common misconceptions

  • "WeakMap is just a Map with fewer methods." The missing methods are on purpose. Without them, your code can't find out when the engine cleans up an object.
  • "I can make a WeakMap pair disappear right now by dropping the key." You can let go of every link to the key, but the engine still decides when to clean up. To remove a pair right now, call delete.
  • "Any value can be a WeakMap key." Strings, numbers, booleans, null, and undefined throw a TypeError. So does a symbol from Symbol.for().
  • "A WeakSet is for unique strings and numbers." That is a Set's job. A WeakSet only holds objects, and symbols made with Symbol().
  • "Private data should always use a WeakMap." In modern class code, #private fields are usually the better choice.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict what throws

Run or read the code. Which error name does it print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
const wm = new WeakMap();
try { wm.set("id", 1); }
catch (error) { console.log(error.name); }

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeBuild a cache for each object

    The function is called twice. Why does the counter not reach 2?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const cache = new WeakMap();
    let calls = 0;
    function score(user) {
      if (cache.has(user)) return cache.get(user);
      calls = calls + 1;
      const result = user.visits * 10;
      cache.set(user, result);
      return result;
    }
    const ada = { visits: 4 };
    console.log(score(ada));
    console.log(score(ada));
    console.log(calls);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeMark items you have handled

      Which two words does the code print?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const processed = new WeakSet();
      function init(item) {
        if (processed.has(item)) return "skip";
        processed.add(item);
        return "init";
      }
      const card = {};
      console.log(init(card));
      console.log(init(card));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeExplain why there is no size

        Fill in the missing word.

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeChoose the right collection

          You need to count how many times each word appears in some text, then print a report. Which collection should you pick?

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            7 QUESTIONS
            WeakMap & WeakSet quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What is weak about a WeakMap key?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does the WeakMap lookup print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const wm = new WeakMap();
              const key = {};
              wm.set(key, "ok");
              console.log(wm.get(key));

              Choose an answer to see the explanation.

            3. Question 3 of 7What does this code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const wm = new WeakMap();
              try { wm.set("id", 1); }
              catch (error) { console.log(error.name); }

              Choose an answer to see the explanation.

            4. Question 4 of 7Why does a WeakMap have no size and no keys()?

              Choose an answer to see the explanation.

            5. Question 5 of 7What does this WeakSet code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const seen = new WeakSet();
              const card = {};
              seen.add(card);
              console.log(seen.has(card));
              console.log(typeof seen.size);

              Choose an answer to see the explanation.

            6. Question 6 of 7Which symbol can be a WeakMap key in modern JavaScript?

              Choose an answer to see the explanation.

            7. Question 7 of 7You want a cache keyed by user objects. It must not keep old users alive. Which do you pick?

              Choose an answer to see the explanation.

            Key takeaways

            • A WeakMap holds its keys weakly: a pair does not keep its key object alive.
            • Use a WeakMap to store a value for each object. Use a WeakSet to mark objects you have seen.
            • There is no size, no keys(), and no loop, so your code can't tell when cleanup happens.
            • Symbols made with Symbol() can be WeakMap keys. Symbols from Symbol.for() can't.
            • For private data in a modern class, use #private fields unless you need the WeakMap way.

            Final definition: WeakMap and WeakSet attach information to objects without keeping those objects alive.

            Up next: JSON.

            CompleteFrontend Clear concepts. Working examples.