· By John Kavanagh

Understanding WeakMap and WeakSet in JavaScript

Abstract image used to represent WeakMap and WeakSet in JavaScript
Image by Andrew Stutesman.

Modern JavaScript offers us plenty of data structures that can help simplify common programming tasks. Amongst these, WeakMap and WeakSet stand out because of their unique approach to handling memory. At first glance, they might seem confusing or unnecessary, but they're actually extremely useful, especially when managing memory or storing metadata.

Today, I intend to explain what WeakMap and WeakSet are, demonstrate some practical ways to use them, and help you understand when each one can benefit your JavaScript applications.


Understanding JavaScript's WeakMap

A WeakMap is a special type of map, with two key differences:

  • Keys can be objects or nonregistered symbols; other primitive values are not allowed.
  • It doesn't prevent JavaScript from garbagecollecting these keys when they're no longer used elsewhere.

A WeakMap does not keep an otherwise unreachable key alive. Once the key is collected, the entry no longer keeps its associated value alive either, though that value may still be referenced elsewhere. Garbage collection has no promised schedule.

A Practical Example of WeakMap

A common use is attaching metadata to DOM elements without retaining them solely through a lookup table. Here is a TypeScript example:

const buttonData = new WeakMap<Element, { clicked: boolean }>();

const button = document.getElementById('submitBtn');

button?.addEventListener('click', () => {
  buttonData.set(button, { clicked: true });
});

// DOM removal alone does not remove strong references such as button.
// The WeakMap does not keep an otherwise unreachable element alive.

Removing the button from the DOM is not enough on its own. The button variable or other application code may still hold a strong reference to it. The weak entry becomes useful when those other references are gone.


Understanding JavaScript's WeakSet

A WeakSet is similar to a regular JavaScript Set, but with two key differences:

  • It stores objects or nonregistered symbols, but not other primitive values.
  • Like WeakMap, it doesn't prevent objects from being garbagecollected.

This makes it ideal when you want to keep track of objects without explicitly worrying about removing them later.

A Practical Example of WeakSet

A typical use of WeakSet is tracking objects you've already processed without attaching additional information, for example:

const visitedNodes = new WeakSet<object>();

function traverseNode(node: object) {
  if (visitedNodes.has(node)) return;

  visitedNodes.add(node);
  // Process the node here
}

The WeakSet does not keep node alive after other strong references are gone. That permits collection; it does not tell us when collection will happen.


When to Choose WeakMap vs. WeakSet

Choosing between these two structures depends on your needs:

  • Use WeakMap when you need to store extra data associated with objects that can be safely cleaned up when objects are no longer used.
  • Use WeakSet when you just need to keep track of objects you've encountered without associating extra data.

Here's another way of simplifying your choice:

  • Storing data about objects? Choose WeakMap.
  • Tracking unique objects without extra data? Go with WeakSet.

How Are They Different from Regular Maps and Sets?

A reachable Map or Set strongly retains its entries until they are removed. A weak collection avoids keeping its keys or members alive on its own. Regular collections can also be collected when the collection itself becomes unreachable.

Also:

  • You can iterate over regular Maps and Sets, but not over WeakMaps and WeakSets.
  • WeakMap keys and WeakSet members must be objects or nonregistered symbols. A WeakMap value can be any JavaScript value, including a string, number, null or undefined.

Practical Usage: Encapsulating Data with WeakMap

Another useful application for WeakMap is encapsulating instance data behind module or closure scope, like this:

const privateData = new WeakMap<object, { secret: string }>();

class SecretHolder {
  constructor(secret: string) {
    privateData.set(this, { secret: secret });
  }

  reveal() {
    return privateData.get(this)?.secret;
  }
}

const mySecret = new SecretHolder('WeakMaps are handy!');
console.log(mySecret.reveal());  // WeakMaps are handy!
console.log(privateData.has(mySecret));  // true within this module scope

This pattern keeps the data away from consumers that cannot access privateData. Code in the same module or closure scope can still read the WeakMap, so this is scopebased encapsulation rather than a language private field such as #secret.


Does Using Weak Collections Improve Performance?

The specific benefit is avoiding a strong reference from the collection to its keys or members. That can help with metadata whose lifetime should follow another object. It does not make weak collections a general cure for memory leaks.

Choose them for that ownership behaviour, then profile if runtime or memory use matters. They do not promise faster lookups or a particular amount of memory saved.


Wrapping Up

WeakMap and WeakSet are useful when a lookup should not keep an object alive. Use WeakMap for associated data and WeakSet for membership, and keep ordinary strong collections when you need to enumerate their contents.

Key Takeaways

  • Weak collections permit garbage collection when no other strong references keep their keys or members alive; collection timing is unspecified.
  • Use WeakMap for storing metadata or extra details about objects.
  • Choose WeakSet when you just want to keep track of unique objects.
  • They address one source of unwanted retention, rather than preventing every memory leak.

We still need to understand the references our application keeps. A weak lookup is helpful when its lifetime matches the objects we are tracking.


Planning a platform change?

I help teams make difficult platform work clearer, from architecture decisions and migrations to launch recovery, performance, and search visibility.