WeakMap is for one thing: attaching data to an object such that the data disappears when the object does. It has exactly four methods—set, get, has, delete—and that has not changed.

This article was published in 2024 and completely rewritten in September 2026. The three “new methods” it described do not exist. Calling them throws a TypeError.

  • WeakMap.prototype.merge does not exist
  • WeakMap.prototype.hasKey does not exist (existence checks use has())
  • WeakMap.prototype.entries does not exist. A WeakMap is fundamentally not enumerable—the reason is explained below
  • The memory-comparison table previously included has been removed, as the figures could not be sourced

The only genuine change in recent years is ES2023 allowing symbols as keys. This article covers the accurate specification and where WeakMap genuinely helps.

What a WeakMap is

A map that holds its keys weakly. Being a key in a WeakMap does not, on its own, keep an object alive.

const meta = new WeakMap();

let element = document.createElement('div');
meta.set(element, { clickCount: 0 });

meta.get(element);   // { clickCount: 0 }

element = null;
// with no other references, both the div and the WeakMap entry
// become eligible for garbage collection

 

This is the decisive difference from Map. An object used as a key in a Map is never collected. In a long-running application, that is a memory leak.

 

There are four methods

Method Behaviour
set(key, value) Stores an entry (returns the map, so it chains)
get(key) Reads it; undefined when absent
has(key) Whether an entry exists
delete(key) Removes it; returns a boolean

That is the entire API. There is no size, no keys() / values() / entries() / forEach(), and it cannot be iterated.

const wm = new WeakMap();

wm.size;              // undefined
wm.entries();         // TypeError: wm.entries is not a function
for (const x of wm) {} // TypeError: wm is not iterable

 

Why it cannot be enumerated

This is not a limitation; it follows from the design.

An entry can be removed at any moment once its key is unreachable elsewhere. When that happens is implementation-dependent and unspecified.

If you could enumerate the contents, you could observe when garbage collection ran. The same code would produce different results depending on timing—a non-deterministic program.

So the count, the order and the contents are all deliberately unobservable. “I want to loop over it for debugging” is a use case WeakMap simply does not serve.

If you need to see inside, use a Map—which means you did not need weak references.

 

ES2023: symbols as keys

The one real change of recent years. Keys used to be objects only; now non-registered symbols work too.

const wm = new WeakMap();

const key = Symbol('token');
wm.set(key, 'value');       // allowed from ES2023
wm.get(key);                // 'value'

// strings and numbers are still rejected
wm.set('str', 1);           // TypeError
wm.set(1, 'x');             // TypeError

// symbols from Symbol.for() are excluded
wm.set(Symbol.for('app'), 1); // TypeError

 

Registered symbols are excluded because they live in a global registry, making them effectively string-like and never collectable.

Chrome and Safari support this; Firefox does not at the time of writing. Check before relying on it.

 

WeakMap or Map?

WeakMap Map
Valid keys Objects, non-registered symbols Any value
Key references Weak (does not prevent collection) Strong (prevents collection)
Enumeration Not possible Possible
size No Yes
clear() No Yes
Best for Metadata attached to objects Ordinary key-value storage

One question decides it: should this map get to determine how long the key object lives? If not, use a WeakMap.

 

Use case 1: metadata on DOM elements

The most practical application. When the element leaves the DOM, the metadata goes with it.

const state = new WeakMap();

function initCounter(el) {
  state.set(el, { count: 0 });

  el.addEventListener('click', () => {
    const s = state.get(el);
    s.count += 1;
    el.textContent = `${s.count} clicks`;
  });
}

document.querySelectorAll('.counter').forEach(initCounter);

 

After remove(), with no other references, the element and its { count: n } are collected together. Do the same with a Map and removed elements stay in memory forever.

One misconception to clear up

Using a WeakMap does not prevent event listener leaks.

In the code above, the listener is attached to el itself. If the element leaves the DOM and nothing references it, the element, its listener and the WeakMap entry all become collectable. Conversely, if something still holds a reference to the element, nothing is collected—WeakMap or not.

What a WeakMap guarantees is only that the map’s own existence does not block collection. When listeners genuinely need removing, call removeEventListener or detach them all with an AbortSignal.

const controller = new AbortController();

el.addEventListener('click', handler, { signal: controller.signal });
el.addEventListener('keydown', handler2, { signal: controller.signal });

// removes both
controller.abort();

 

AbortSignal usage is also covered in practical fetch().

 

Use case 2: caching computed results

Caching expensive work per object, where the cache should vanish with the original object, fits perfectly.

const cache = new WeakMap();

function getExpensiveResult(obj) {
  if (cache.has(obj)) {
    return cache.get(obj);
  }

  const result = heavyCalculation(obj);
  cache.set(obj, result);
  return result;
}

 

Written with a Map, the cache grows without bound, and you end up implementing size limits or LRU eviction. A WeakMap delegates that to the engine.

The trade-off: hit rates are not guaranteed. Since collection is unobservable, this is unsuitable for data that must still be there.

 

Use case 3: private data (prefer #private now)

WeakMap was long used to keep class internals inaccessible from outside.

const privateData = new WeakMap();

class Account {
  constructor(secret) {
    privateData.set(this, { secret });
  }
  getSecret() {
    return privateData.get(this).secret;
  }
}

 

Today, private fields with # are the straightforward choice. Standardised in ES2022 and available everywhere.

class Account {
  #secret;

  constructor(secret) {
    this.#secret = secret;
  }

  getSecret() {
    return this.#secret;
  }
}

const a = new Account('xxx');
a.secret;    // undefined
a.#secret;   // SyntaxError — not accessible outside the class

 

# states the intent clearly and avoids carrying an external WeakMap around. The WeakMap version still has a place when you are not using class syntax, or when several classes share metadata.

 

WeakRef and FinalizationRegistry

ES2021 added two related primitives.

const ref = new WeakRef(someObject);

// retrieve it; undefined if already collected
const obj = ref.deref();
if (obj) {
  obj.doSomething();
}

 

You should generally avoid both. MDN and the specification both counsel caution.

  • Collection timing is implementation-dependent and unpredictable, varying by browser and version
  • FinalizationRegistry callbacks may never run—closing a tab, for instance. Design assuming they will not
  • They are not a reliable cleanup mechanism. An explicit close() or dispose() is far more dependable

When you think you need a weak reference, check whether a WeakMap suffices first. Usually it does.

 

Before you reach for it

  • Is the key an object? For strings or numbers, use Map
  • Do you need to count or list the contents? Then use Map—a WeakMap cannot
  • Is the key object’s lifetime managed elsewhere? That is exactly when WeakMap fits
  • Would losing the data be a problem? Then WeakMap is wrong—you cannot know when it goes

 

Summary

  • merge, hasKey and entries are not WeakMap methods
  • There are four: set, get, has, delete. No size, no iteration
  • Non-enumerability follows from the design—otherwise GC timing would be observable
  • The only recent change is ES2023 allowing non-registered symbols as keys (not yet in Firefox)
  • It shines for DOM element metadata and per-object caches
  • A WeakMap does not remove event listeners. Use AbortSignal for that
  • Prefer class # fields for private data
  • WeakRef and FinalizationRegistry behave unpredictably—do not reach for them casually

“Memory efficient” is the phrase that circulates, but the real property is that you can delegate lifetime to something else. For array methods, see grouping and non-mutating array methods.

ABOUT ME
りん
On this blog, I mainly share information about web development and programming, along with my daily thoughts and what I’ve learned. I aim to create a blog that lets readers enjoy both technology and everyday life, so I also include topics about daily experiences, books, and gourmet. I’d be delighted if you could drop by casually and find something useful or enjoyable here.