Javascript Underdogs: Part 1 - The WeakMap
Tags: javascript
Published: 2018-04-11 00:18:02

---
title: Javascript Underdogs: Part 1 - The WeakMap
published: true
description: Diving deep into the powerful WeakMap
tags: Javascript
cover_image: https://thepracticaldev.s3.amazonaws.com/i/1ptv71ri7pdmho51a53u.png
---

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 `…` .

![gif1](https://media.giphy.com/media/xTiTnEHBh7qapyuvwQ/giphy.gif)

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`💃.

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

```javascript
var USSR = {
  name: 'Soviet Union',
  capital: 'Moscow',
  ..
  ..
}

var countries = [ Afganishtan, Albania, Algeria, ..., USSR, ..., Zimbabwe ]
```

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.

```javascript
async function calcArea(country) {
  const boundaries = await fetch(country);

  area = calculateArea(country, boundaries); // takes a long time

  return area;
}
```

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

1.  Debounce the function
2.  Cache the function

[Debouncing](https://davidwalsh.name/javascript-debounce-function) 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`.

```javascript
function cachify(fn) {
  // its a good idea to hide you cache inside the closure
  var cache = new Map();
  return arg => {
    if (cache.has(arg)) {
      return cache.get(arg);
    }
    var computed = fn(arg);
    cache.set(arg, computed);
    return computed;
  };
}

cachedCalcArea = cachify(calcArea);

cachedCalcArea(USSR); // goes and computes the area
cachedCalcArea(USSR); // already computed, returns the cached area
```

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

```javascript
countries.remove(USSR);
// add the new countries
countries.add([Armenia, Azerbaijan, ...., Uzbekistan]);
```

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:

1.  Maintain a **precomputed area array** and keep it in sync with countries.
2.  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](https://chrisrng.svbtle.com/lru-cache-in-javascript), 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?

![wmap](https://user-images.githubusercontent.com/6966254/38578957-73f8d2e4-3cd3-11e8-9b63-e0a4a2e7a73a.png)

`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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)

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.

```javascript
function weakCache(fn) {
  var cache = new WeakMap(); // <-- Behold the Weak!
  return (arg) => {
    if (cache.has(arg)) {
      return cache.get(arg);
    }
    var computed = fn(arg);
    cache.set(arg, computed);
    return computed;
  }
}
cachedCalcArea = weakCache(calcArea);

cachedCalcArea(USSR); // cache miss
cachedCalcArea(USSR); // cache hit
```

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)
* 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.

  ```javascript
  var building = new WeakMap();
  ```

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

  ```javascript
  var key = {
    password: '🔑'
  };
  ```

* We can lock/unlock our door with this key.

  ```javascript
  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.

  ```javascript
  var fake_key = {
    password: '🔑'
  };
  ```

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

  ```javascript
  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 :(.

  ```javascript
  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 GC 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.

```javascript
countries.remove(USSR); // remove from array

USSR = undefined; // unset the variable

// at this point there is no way to get the cached area of USSR since it doesn't exist anymore
```

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.

```javascript
var obj = {name: '🐕'};
weakMap.set(obj, 'animal');

obj.name = '🙍‍♂️';
weakMap.get(obj); // 'animal'
```

* WeakMap cannot accept **primitive javascript values as keys**. You should use `Map` if you wanna use them as your key. 

```javascript
weakMap.set('key', 'value'); // Error!
```

* 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.
* You can use anything as a `value` for `WeakMap`/`Map`. 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.
* 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.

```javascript
var user = {
	name: "Kushan Joshi"
}

var websites = ['Facebook', 'Github', 'Twitter', 'Dev.to', 'Medium'];

var memberOf = (user) => websites.filter(website => isUser(user));

// save the websites and keep track of it, too complicated 🤮 !
user.memberOf = memberOf(user);

// deriving the value using weakMaps, awesomo 🤖!
cachedMemberOf = weakCache(memberOf); // avoid recomputing everytime
// or derive it everytime you need it
console.log(cachedMemberOf(user)); 
render(cachedMemberOf(user))
```



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](https://twitter.com/kushan2020).