WeakMap and WeakSet: Private Object State Without Memory Leaks
AI generated
JS
() =>
JavaScript · Garbage Collection · Private State · Design Patterns
WeakMap and WeakSet
Private Object State Without Memory Leaks

An ordinary Map used to associate objects with private state prevents those objects from ever being collected by the garbage collector as long as the map exists. WeakMap and WeakSet solve exactly this problem by only weakly referencing their keys, enabling private state that automatically disappears along with its associated object.

14 min read WeakMap · WeakSet · Garbage Collection · Private Fields ES2015 · All Modern Engines

1. The Memory Leak Problem of Ordinary Map Associations

If you need additional state attached to an existing object in JavaScript, without changing the object itself, an ordinary Map seems like the obvious choice: the object is used as the key, the additional state as the value. The problem: a Map holds strong references to its keys. As long as the map exists, it prevents the garbage collector from ever collecting the object used as a key, even if no other reference to it exists anywhere else in the program.

WeakMap and WeakSet solve this problem structurally. Both data structures hold their keys or elements only through weak references. A weak reference does not prevent garbage collection of the referenced object. As soon as no strong reference to an object used as a key in a WeakMap exists anymore, the object is collected, and the corresponding entry in the WeakMap disappears along with it automatically.

2. WeakMap and WeakSet: Weak References Explained

A WeakMap works almost identically to an ordinary Map in terms of call syntax: set, get, has and delete are all available. The decisive difference lies in the restriction that only objects are allowed as keys, not primitive values. This restriction is not an oversight but follows directly from the purpose of the data structure: primitive values are not managed by the JavaScript engine via references anyway, so the concept of a weak reference makes no sense for them.

A WeakSet analogously stores only objects as elements, again via weak references. Unlike a Map with key value pairs, a WeakSet primarily serves to mark objects, for instance "this object has already been processed", without storing additional values of its own. Both structures were introduced with ES2015 and are fully implemented in all modern JavaScript engines.


const metadata = new WeakMap();

function attachMetadata(obj, info) {
  // The key (obj) is only weakly referenced by the WeakMap
  metadata.set(obj, info);
}

let user = { name: "Alice" };
attachMetadata(user, { role: "admin", lastSeen: Date.now() });

console.log(metadata.get(user)); // { role: "admin", lastSeen: ... }

user = null; // No more strong references to the original object
// The WeakMap entry can now be garbage collected along with the key

3. Limitations: No Iteration, No size

The weak referencing that makes WeakMap and WeakSet so useful comes with a deliberate limitation: neither structure is iterable. There is no keys(), values(), entries() or forEach method, and no size property either. The reason is technical: the exact timing of garbage collection is not deterministic in JavaScript and is under the control of the engine, not the programmer.

If you could iterate over a WeakMap, the result of the iteration would depend on the exact, unpredictable timing of garbage collection, which would lead to non reproducible program behavior. This limitation is therefore not a missing feature but a deliberate design decision that makes WeakMap and WeakSet suitable exclusively for use cases where you look up a known key deliberately, not for cases where you need to list all stored entries.

4. Practical Case: Private Fields Before Class Syntax

Before JavaScript got native private class fields with # syntax, WeakMap was the standard pattern for implementing truly private state for instances of a class. The state was stored in a module wide WeakMap outside the class, keyed by the respective instance. This state was inaccessible from outside the module, because the WeakMap itself was never exported, and unlike conventions such as an underscore prefix, the state was truly, not just conventionally, private.

This pattern still has practical relevance today, for instance when private state needs to be shared across several related classes that do not sit in the same class hierarchy, a use case that native private fields with # do not directly support, because they are strictly bound to a single class.


// Module-level WeakMap holds truly private state, keyed by instance
const privateState = new WeakMap();

class BankAccount {
  constructor(initialBalance) {
    // The balance is not accessible from outside this module at all
    privateState.set(this, { balance: initialBalance });
  }

  deposit(amount) {
    const state = privateState.get(this);
    state.balance += amount;
  }

  getBalance() {
    return privateState.get(this).balance;
  }
}

const account = new BankAccount(100);
account.deposit(50);
console.log(account.getBalance()); // 150
console.log(account.balance);      // undefined — truly private

5. Managing DOM Metadata Without Memory Leaks

A classic use case for WeakMap is attaching additional metadata to DOM elements, without changing the elements themselves and without abusing custom data attributes. If a DOM element is eventually removed from the document and no other JavaScript reference to it exists anymore, the garbage collector can collect both the element and its associated metadata in the WeakMap at the same time.

With an ordinary Map, this would not be the case: if you remove an element from the DOM but the Map still holds a strong reference to it, the element stays in memory even though it has long become irrelevant to the application. In single page applications with frequent DOM creation and removal, this effect accumulates over time into noticeable memory leaks, which WeakMap rules out from the start.


const elementState = new WeakMap();

function trackClicks(el) {
  elementState.set(el, { clickCount: 0 });
  el.addEventListener("click", () => {
    const state = elementState.get(el);
    state.clickCount++;
    console.log(`Clicked ${state.clickCount} times`);
  });
}

const button = document.querySelector("#my-button");
trackClicks(button);

// If #my-button is later removed from the DOM and dereferenced,
// its entry in elementState is eligible for garbage collection too

6. Caches with Automatic Cleanup via WeakMap

Another practical use case is caching computation results that depend on an object. Instead of caching the result in an ordinary Map and having to make sure stale entries get removed yourself, WeakMap handles this cleanup automatically: as soon as the source object is no longer referenced, the corresponding cache entry disappears too, without requiring explicit cleanup logic.

This is especially valuable for expensive computations whose result is bound to a temporary object, for instance parsed configuration data or computed layout information for a UI component object. The cache never grows unbounded, because it self limits over the lifetime of the referenced objects, something that would only be possible with an ordinary Map using additional, manually maintained logic.

7. WeakSet: Marking Instead of Mapping

WeakSet is used less often than WeakMap, but has a clear use case of its own: marking objects without storing additional data alongside them. A typical example is tracking whether an object has already been processed, for instance in a recursive algorithm that needs to detect circular references. Instead of abusing a Map with the value true for every key, a WeakSet expresses pure membership more clearly.

This exact pattern is also used in a robust deep equality implementation, where a WeakSet or WeakMap marks already visited object pairs during a recursive traversal to avoid infinite loops with circular references. Here too: once the traversed objects are no longer referenced after the operation completes, the garbage collector automatically frees the memory of the markers.


const processed = new WeakSet();

function processOnce(obj) {
  if (processed.has(obj)) {
    console.log("Already processed, skipping");
    return;
  }
  processed.add(obj);
  console.log("Processing for the first time");
  // ... actual processing logic here
}

const item = { id: 1 };
processOnce(item); // Processing for the first time
processOnce(item); // Already processed, skipping

8. WeakMap vs. Private Class Fields with #

Since private class fields with #name syntax became natively available in JavaScript, the original main use case of WeakMap, truly private class state, has become less relevant for most new classes. Private fields are easier to read, require no external data structure, and are directly supported by the engine, including better error messages for misuse.

WeakMap remains superior, though, when private state needs to be shared across several objects that do not sit in the same class hierarchy, or when state needs to be attached retroactively to already existing objects you do not control yourself, such as DOM elements or objects from a third party library. For new, self written classes, private fields are usually the simpler choice, while for metadata attached to foreign objects, WeakMap remains the right tool.

9. WeakMap/WeakSet Directly Compared to Map/Set

The following table contrasts the key differences between the weak and the ordinary collection types and shows which variant is the right choice in which situation.

Property Map / Set WeakMap / WeakSet Consequence
Reference type to keys Strong Weak WeakMap does not prevent garbage collection
Allowed keys Any value Objects only Primitives need a regular Map
Iterable Yes No No forEach, keys, size on the weak variants
Memory behavior Can cause leaks Self cleaning WeakMap suited for DOM metadata and caches
Typical use Counters, ordered collections Private state, metadata, marking Use case decides

The choice between both families is rarely a pure performance question, but a question of lifetime semantics: if the additional state should be tied exactly to the lifetime of an object, WeakMap is the right choice. If you need to list or count all stored entries, an ordinary Map remains the only option.

Mironsoft

Memory management, frontend architecture and performance

Applications that stay lean even after hours of runtime?

We find memory leaks in existing single page applications, replace problematic Map based metadata associations with WeakMap patterns, and set up private state management that cleans itself up automatically.

Memory Profiling

Tracking down memory leaks in DOM metadata and state management

Code Modernization

Replacing Map based patterns with WeakMap and private class fields

Architecture Consulting

Designing cache strategies with automatic memory cleanup

10. Summary

WeakMap and WeakSet solve a real memory problem that arises with ordinary Map and Set objects when objects serve as keys: weak referencing ensures that objects can still be collected normally by the garbage collector once no strong references to them exist anymore. The deliberate restriction to non iterability is a direct consequence of the fact that the timing of garbage collection is not deterministic.

The main practical use cases are private class state before # syntax existed, metadata on DOM elements without memory leaks, self cleaning caches, and marking already visited objects in recursive algorithms. For new, self controlled classes, native private fields are often the simpler choice, while for state attached to foreign or dynamically created objects, WeakMap and WeakSet remain the right tool.

WeakMap and WeakSet — Key Points at a Glance

Core Principle

Weak references to object keys do not prevent garbage collection, unlike Map/Set.

Limitations

Not iterable, no size property, only objects allowed as keys.

Typical Use Cases

Private class state, DOM metadata, self cleaning caches, marking visited objects.

Distinction from # Fields

Private fields for your own classes, WeakMap for state on foreign or external objects.

11. FAQ: WeakMap and WeakSet

1Main difference between WeakMap and Map?
WeakMap only weakly references keys and therefore does not prevent garbage collection.
2Why is iteration not possible?
GC timing is not deterministic, iteration would produce non reproducible behavior.
3Which keys are allowed?
Only objects, no primitive values.
4What is WeakSet used for?
For marking objects, for instance detecting already processed elements.
5Do # fields replace WeakMap?
Not completely, WeakMap stays relevant for foreign objects and shared state.
6Does it help with DOM memory leaks?
Yes, element and WeakMap entry are collected together once no reference remains.
7Can you query the size?
No, there is no size property.
8Since when is it available?
Since ES2015, fully implemented in all modern engines.
9Is WeakMap suitable for caches?
Yes, the cache cleans itself up along with the referenced objects.
10What methods does WeakSet offer?
add, has and delete, without iteration methods.