Hello 2018! It has been 3 years since we first saw Javascript 2015 (aka ES6). During this time most of us have focused on the cosmetic changes like Arrow => Functions or the fancy destructing operator … . (Don’t kill for calling them cosmetic 💄)

Everyone needs something exciting like the upcoming fancy |> pipe operator. Who cares if ES6 also added things like WeakMap , WeakSet , Iterables , Map or Set . Even looking at this thing called WeakMap , feels so depressing 😞

Keeping the sarcasm aside, let us talk about WeakMaps :dancer

Why you would need something Weak

I have to agree the name WeakMap is definitely a misnomer. If it were me I would have named it SuperMap. Before we get into definitions, let us actually take a moment and understand why we need WeakMap 's in our apps.

Imagine it’s 1990 🏡 and you create an app of all the countries 🎏 present at that time.

A user can click any country and get detailed information which also includes the area of the country. Below is a hypothetical area calculation function.

Caching the Area

Every time a user clicks a country you calculate the area. But we have a problem! If a user clicks a country multiple times you have to repeat this enormous asynchronous calculation, which is something we should totally avoid. There are generally two ways to solve this kind of problem.

Debounce the function Cache the function

Debouncing is a peaceful way to calm down multiple aggressive invocations in a short interval of time. (Imagine an impatient user clicking the refresh button multiple times). Debounce allows us to only take the last invocation and discard the rest.

Since countries don’t change area that often, we can simply cache the result of calcArea .

We can use both caching and debouncing to make our application performant. Below is a generic caching function which we will use to cache calcArea .

Great! We made some serious performance improvements.

But we have another problem, USSR just broke into 15 new countries. This would mean we remove USSR and add the newly formed countries to our countries array.

Removing USSR just from the array doesn't help, as our cache still contains USSR and the calculated area. A naive solution would be to monkey patch our cachify function to remove USSR, but if the world continues to break into smaller countries we have got ourselves a memory leak.

We need a smart way to clean up our cache which scales well. There are multiple ways fellow developers would approach this problem:

Maintain a precomputed area array and keep it in sync with countries. Figure out some smart cache eviction like LRU, time-based, etc.

Precomputing the area for every country seems to be a waste of computation, as most of the users won’t ever be seeing every country.

We can use a smart caching strategy like Least Recently Used caching, this caching automatically removes the entry which is least recently used. But we aren’t running out of memory with 160+ countries and LRU doesn’t seem all that magical and seamless.

What about WeakMap?

WeakMap is the missing jigsaw piece for our caching problem. It automatically removes* any unused references from it.

“The WeakMap object is a collection of key/value pairs in which the keys are weakly referenced. The keys must be objects and the values can be arbitrary values.” — MDN

I like to say WeakMap is nothing but a regular Map with dementia. It is a very forgiving data structure, it will forget things which no longer matter. (We should be like that too :P)

We can simply replace the Map with WeakMap in our caching function.

Now let USSR break into the 15 countries. We just need to take care of removing all references pointing to the USSR obj in our app and our cachedCalcArea function will automatically forget the USSR entry in the cache. Hence, avoiding the memory leak!

How does it forget things?

WeakMap works similar to a regular Map but in order to be a forgetful version of Map it imposes these constraints:

Primitive data type keys are not allowed (Numbers, String, null, true, etc)

type keys are not allowed (Numbers, String, null, true, etc) You cannot list all the values inside the WeakMap

Let us see a hypothetical example of WeakMap

Imagine a WeakMap instance to be a building with thousands of 🚪 doors.

var building = new WeakMap();

Each door has a unique key and we own a key :key: for our 🚪101 . Due to the constraints mentioned above the key can only be an object.

var key = { password: '🔑' };

We can lock/unlock our door with this key.

building.set(key, '🚪101'); building.get(key); // 🚪101

Now a thief has seen our key (Its Javascript duh!) and he tries to fabricate a duplicate key.

var fake_key = { password: '🔑' };

Since we live in a Javascript world we clearly know even though they look same, they are not equal .

fake_key === key // false

Our thief didn’t read this awesome article and he tries to get into our building using his fake key only to fail :(.

building.get(fake_key); // undefined

What happens if we lose the key

As long as some variable holds the reference to our original key we are safe. But if there comes a time when no variable in the entire app is holding a reference to our key, we lose the access to our 🚪101 .

This is exactly what powers the smart caching of a WeakMap . If we lose the key, the javascript compiler can deduce that there is no way to access the thing associated with the key and it can safely remove it from the memory.

Note: This is the crucial difference between a WeakMap and Map . WeakMap removes <key,value> if you lose the key, but in a Map, you can simply list all the keys to find the lost key.

Coming back to USSR problem, when USSR breaks into the 15 countries and we just need to take care of removing all references to the USSR obj in our app.

As you can see after the above steps, there is no way of accessing the USSR object in the current state of app and with this knowledge Javascript garbage collector automatically clears the memory it reserved for the area of USSR. Notice the removing happens behind the scenes and all we did was replace Map with WeakMap . Isn't that powerful?

WeakMap Takeaways

Remember not to mutate the key object because in Javascript the object reference stays the same even if you mutate the object.

WeakMap can not accept primitive javascript values as keys. You should use Map if you wanna use them as your key.

Sometimes it is faster to not cache a function. If your function barely takes a millisecond to execute, you would end up slowing it down by caching.

a function. If your function barely takes a millisecond to execute, you would end up slowing it down by caching. You can use any kind of thing as a value for WeakMap or Map . Yes even promises!

a value for or . Yes even promises! The eviction of an unsued key doesn’t happen immediately . It depends on the garbage collector’s mood. You shouldn’t worry about this part though.

. It depends on the garbage collector’s mood. You shouldn’t worry about this part though. WeakMap works great for derived state. A lot of times your application has state which can simply be derived from other state. In example below, you can see deriving a value using cached function is much more maintainable and easier to reason with.

I really hope this article helped you in understanding WeakMaps . I love using it with libraries like Immutable.js or Redux since they enforce immutability. Even if you don't use these libraries, as long as you don't mutate the object you can reap benefits from WeakMap.

I am planning on writting a Part-2 of Javascript Underdogs, let me know in the comments what Javascript feature you think is amazing but underapreciated.

If you ❤️ this article, please share this article to spread the words.

Reach out to me on Twitter @kushan2020.