cf.completefrontendCode editorOpen lab
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 weak reachabilitySay why a WeakMap entry does not keep its object key alive.
  • 02
    Use WeakMap and WeakSet APIsSet, get, has, and delete with valid keys and recognize TypeErrors.
  • 03
    Choose the right collectionPick Map, WeakMap, Set, or WeakSet for caches, metadata, and tracking.

Objects can carry quiet notes

A Map remembers a value for any key. That is perfect when you want a collection you can count, loop over, and keep. A WeakMap is different: it attaches data to an object without making that object stay alive just because the collection knows about it.

The curriculum summary says it well: attach data to objects without keeping them alive. That one sentence explains the three big uses in this lesson: per-object caches, private data for instances, and tracking which objects have been processed.

Plain definition

A WeakMap stores values by object or non-registered symbol key, but the key is held weakly: if nothing else can reach the key, the entry can disappear. A WeakSet stores weak membership for objects.

Real-life analogyA name tag that disappears after a guest leaves

Imagine a guest wearing a name tag. A regular Map keeps the guest listed. A WeakMap can forget the tag after the rest of the program has forgotten the guest.

In real life: A guest with a name tag
In JavaScript: An object used as a key
In real life: A guest list keeps each guest listed
In JavaScript: A Map keeps key objects strongly reachable
In real life: The tag goes when the guest leaves
In JavaScript: A WeakMap does not keep the key alive
In real life: No public list of old tags
In JavaScript: No size, keys, or iteration

Where the analogy stops: Event staff decide when a guest leaves. JavaScript decides when cleanup happens, and normal code cannot see the exact moment.

This lesson builds on Map and Set, the Symbols lesson, Objects & references, and Closures. Deeper lessons named Memory management & garbage collection and Weak references & ephemerons come later; here, “reachable” simply means “your program can still get to the object through variables or other objects.”

Weakly held keys

STEP THROUGH

WeakMap does not mean “small Map” or “slow Map.” It means the collection’s link to a key is weak. When the key object is still reachable elsewhere, get, has, set, and delete work normally. The difference appears when the rest of your program stops referencing the key.

Map and WeakMap with the same object
Step 0 of 7Ready
Your turn: follow the blue line

Step through the same object used as a Map key and a WeakMap key. The difference is reachability, not the get result while the object is still alive.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
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));
CallStoreChangeResultRun = next line. 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.

Notice what the replay does not do: it does not show an entry disappearing at a specific moment. Garbage collection timing is not observable in normal JavaScript. The lesson can show the rules and a reachability model, but not the engine’s private memory schedule.

The real API rules

REAL CODE

WeakMap has a deliberately tiny API: set, get, has, and delete. WeakSet mirrors Set’s membership operations: add, has, and delete. The key rule is strict: WeakMap keys must be objects or non-registered symbols; WeakSet values must be objects.

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

Run the API rules from real code. Notice the successful object and non-registered symbol keys, then the two TypeErrors.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
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); }
CallStoreChangeResultRun = next line. 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.
WeakMap and WeakSet methods
CollectionWorks withHasDoes not have
WeakMapObject keys and non-registered symbol keysset, get, has, deletesize, keys, values, entries, forEach
WeakSetObject valuesadd, has, deletesize, iteration, forEach
Why no list?

If JavaScript exposed the list or count of WeakMap keys, code could learn when the engine collected unreachable objects. The language keeps that timing private, so WeakMap and WeakSet are intentionally non-iterable.

A conceptual reachability model

INTERACTIVE

Think in arrows. Variables, object properties, arrays, closures, and Maps can all point to objects. If some chain of arrows from running code can reach an object, it is reachable. When no strong arrows remain, it becomes eligible for garbage collection. A WeakMap key arrow is not a strong arrow.

Conceptual reachability model
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
Reachability modelweakmap
variable user→ object
object { name: "Ada" }reachable
weakmapweak key arrow
entryusable while key reachable
Try it yourself

The variable user points at the object, so both Map and WeakMap lookups would work. This is a conceptual reachability model, not memory inspection.

A model of references: it shows which arrows remain. It does not observe real garbage collection, and it never promises when cleanup happens.

The model uses cards, not engine memory. “Eligible” means the engine is allowed to clean up later. It does not mean cleanup has already happened, and it does not give you a callback.

Per-object caches

STEP THROUGH

A cache remembers an expensive result so the next call can reuse it. If the input is an object, a WeakMap makes a good per-object cache: the result belongs to that exact object, but the cache should not keep old objects alive forever.

Per-object cache hit and miss
Step 0 of 13Ready
Your turn: follow the blue line

Step through a per-object cache. The same user object misses once, then hits without keeping the user alive forever.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
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);
CallStoreChangeResultRun = next line. 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.

This pattern appears when you compute derived data for user records, parse metadata for syntax tree nodes, or measure elements in a page. If a later part of your app still holds the object, the cache works. If the object is gone, the cache is allowed to fade with it.

Map or WeakMap?
  • Need to list every key later
  • Cache measurements for page nodes you do not own
  • Count how often each word string appears
  • Attach validation notes to user objects from another library
  • Show the number of players currently in the table
  • Store internal state for class instances in older code
Try it yourself
0 of 6 correct

Choose Map when you need iteration, counting, or primitive keys. Choose WeakMap for side data attached to object lifetimes.

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

Private data

COMPARE

Before JavaScript had #private fields, libraries often put a WeakMap outside a class and used each instance as the key. The public object stayed clean, and code outside the module could not read the WeakMap unless it had access to the variable holding it.

WeakMap private-data patternPop 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);
Private state patterns
PatternGood atTrade-off
Closure private stateA factory returns functions that remember hidden variablesGreat for functions; many instances may duplicate methods
WeakMap private dataClass-like instances before #private fieldsNeeds a module-scoped WeakMap and careful this use
#private fieldsModern class-private data enforced by the languageWorks only inside class syntax and is covered in Private fields & methods

Compare this with closures from the Closures lesson: closures hide variables because functions remember their lexical environment. WeakMap private data hides a lookup table instead. In modern classes, #private fields are usually clearer.

WeakSet: a “seen this object” stamp

INTERACTIVE

WeakSet is the membership-only sibling: it answers “have I seen this object?” without storing extra values. Use it for objects you process once, such as initialized widgets, visited graph nodes, or page nodes you do not own.

Real-life analogyA stamp on a guest’s hand

A venue stamps a guest’s hand after entry. The stamp says “already processed” for that guest. When the guest leaves for good, the stamp leaves too. WeakSet has the same spirit for object membership.

In real life: The guest
In JavaScript: An object
In real life: The hand stamp
In JavaScript: WeakSet membership
In real life: Checking the stamp at the door
In JavaScript: weakSet.has(object)

Where the analogy stops: A real stamp is visible and can be counted by looking at the room. WeakSet membership is intentionally not listable or countable.

Mark an object as processed
WeakSet trackerPop 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 cannot tell you every card it contains.

Try it yourself

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

A WeakSet is ideal for object membership when you do not need to list everything you have seen.

Symbols as WeakMap keys

ES2023

Modern JavaScript allows non-registered symbols as WeakMap keys. A symbol created with Symbol("local") can be unreachable when nobody keeps it. A symbol from Symbol.for("name") lives in the global symbol registry, so it is not allowed as a weak key.

Non-registered symbol keysPop 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); }

This connects directly to the Symbols lesson: Symbol() makes a unique local symbol, while Symbol.for() searches or creates a shared registered symbol. WeakMap accepts the first kind, not the second.

Common misconceptions

  • “WeakMap is just a Map with fewer methods.” The missing methods are the point: they keep garbage collection timing unobservable.
  • “I can force a WeakMap entry to disappear.” You can drop references; the engine decides when collection happens.
  • “WeakMap keys can be any value.” Primitive strings, numbers, booleans, null, and undefined throw TypeError.
  • “WeakSet is for unique primitive values.” That is Set’s job. WeakSet stores object membership only.
  • “Private data should always use WeakMap.” In modern class code, #private fields are usually the better default.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict what throws

Run or read the snippet. What error name is printed?

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 per-object memo cache

    Explain why the calculation counter does not become 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 processed items

      What are the two printed words?

      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 phrase.

        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 string appears and print a report. Which collection should you choose?

          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 makes a WeakMap key weak?

              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 7Which WeakMap operation throws?

              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 WeakMap have no size or keys()?

              Choose an answer to see the explanation.

            5. Question 5 of 7What does the WeakSet snippet 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 7For a cache keyed by user objects that should not keep old users alive, choose:

              Choose an answer to see the explanation.

            Key takeaways

            • WeakMap keys are weakly held: the entry does not keep an object key alive.
            • WeakMap is for per-object values; WeakSet is for object membership.
            • No size, keys, or iteration: collection timing stays unobservable.
            • Non-registered symbols can be WeakMap keys; registered symbols from Symbol.for cannot.
            • For modern class-private data, prefer #private fields unless you need the WeakMap pattern.

            Final definition: WeakMap and WeakSet attach information to object lifetimes without turning that information into a strong reason for the objects to stay reachable.

            Up next: JSON.

            CompleteFrontend Clear concepts. Working examples.