An Overview of Map and Set in JavaScript
Tags: javascript
Published: 2020-07-12 17:13:58

Looking for comparisons? Refer to the articles below:
- [ES6 — Map vs Object — What and when?](https://medium.com/front-end-weekly/es6-map-vs-object-what-and-when-b80621932373)
- [ES6 — Set vs Array — What and when?](https://medium.com/front-end-weekly/es6-set-vs-array-what-and-when-efc055655e1a)
- [JavaScript Collections – Set and Map](https://codingblast.com/javascript-set-map/)

## Map

### Creating an instance

1) Using the constructor:

```js
const colors = new Map();
```

2) Passing an [iterable](https://javascript.info/iterable):

```js
const colors = new Map([
  ["#ff0000", "red"],
  ["#0000ff", "blue"]
]);

// => colors: {"#ff0000" => "red", "#0000ff" => "blue"}
```

3) Using the `set()` method which is chainable:

```js
const colors = new Map()
  .set("#ff0000", "red")
  .set("#0000ff", "blue");

// => colors: {"#ff0000" => "red", "#0000ff" => "blue"}
```

### Methods

1) Reading and writing:

```js
const colors = new Map([
  ["#ff0000", "red"]
]);

colors.get("#ff0000");
// => "red"

colors.get("#0000ff");
// => undefined
```

Writing has already been covered, though, but for the sake of completeness:

```js
const colors = new Map();

colors.set("#0000ff", "blue");
// colors: {"#0000ff" => "blue"}
```

2) Checking if the entry with the given key exists:

```js
const colors = new Map().set("#ff0000", "red");

colors.has("#ff0000");
// => true
```

3) Getting the keys and values:

```js
const colors = new Map().set("#ff0000", "red");

for (const hex of colors.keys()) {
  console.log(hex);
}

// => "#ff0000"

for (const color of colors.values()) {
  console.log(color);
}

// => "red"

for (const [hex, color] of colors.entries()) {
  console.log(hex, "=>", color);
}

// => "#ff0000 => red"
```

4) Removing a single entry:

The [`delete()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) method returns a boolean according to the operation's result.

```js
const colors = new Map().set("#ff0000", "red");

colors.delete("#ff0000");
// => true

colors.delete("foo");
// => false
```

5) Removing all entries:

```js
const colors = new Map().set("#ff0000", "red");

colors.clear();
// => colors: {}
```

### Properties

When it comes to retrieving the size of a map (that is, the number of entries it has), it's more convenient than that of an object:

```js
const colors = new Map([
  ["#ff0000", "red"]
]);

console.log(colors.size);
// => 1

colors.set("#0000ff", "blue");

console.log(colors.size);
// => 2
```

### Tips & Tricks

1) Converting between Maps and Objects

The conversion is possible so long as the map consists of keys of types of strings and symbols.

```js
const colors = new Map([
  ["#ff0000", "red"]
]);

const obj = Object.fromEntries(colors);
// => obj: {"#ff0000": "red"}
```

2) Mapping and filtering

Steps:
1. Convert the `Map` to an `Array`.
2. [Filter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) or [map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) the array.
3. Convert the result back to a `Map`.

```js
const numbers = new Map([
  [1, "a"],
  [2, "b"],
  [3, "c"]
]);

const mappedNumbers = new Map( // 3
  [...numbers] // 1
    .map( // 2
      ([number, char]) => [number * 10, char]
    )
);
// => mappedNumbers: {10 => "a", 20 => "b", 30 => "c"}
```

```js
const numbers = new Map([
  [1, "a"],
  [2, "b"],
  [3, "c"]
]);

const filteredNumbers = new Map( // 3
  [...numbers] // 1
    .filter( // 2
      ([number]) => number > 2
    )
);
// => filteredNumbers: {3 => "c"}
```

3) Key equality is based on [sameValueZero](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness#Same-value-zero_equality)

`NaN` keys are considered to be equal:

```js
const map = new Map();

map.set(NaN, "foo");
map.get(NaN);
// => "foo"

map.set(NaN, "bar");
map.get(NaN);
// => "bar"
```

But object keys are always different:

```js
const map = new Map().set({}, "foo").set({}, "bar");

console.log(map.size);
// => 2
```

## Set

### Creating an instance

1) Using the constructor:

```js
const set = new Set();
```

2) Passing an iterable:

```js
const numbers = new Set([1, 2, 3]);
// => numbers: {1, 2, 3}

const chars = new Set("123");
// => chars: {"1", "2", "3"}
```

3) Using the `add()` method which, similarly to `set()`, is chainable:

```js
const numbers = new Set()
  .add(1)
  .add(2)
  .add(3);
// => numbers: {1, 2, 3}
```

### Methods

1) Reading and writing values:

Keep in mind the caveat that there's no random access to an element in a `Set` (citing from [ES6 — Set vs Array — What and when?](https://medium.com/front-end-weekly/es6-set-vs-array-what-and-when-efc055655e1a)):

> More important, because the Array data is stored in consecutive memory, the CPU will be able to access the data much faster due to pre-fetching. Hence in general accessing an elements in Array (one after the other such as in a for loop) is quicker and more efficient if you compared to other type of abstract data types.

There are workarounds to locate specific elements though. For instance, to get the first element of a set you can grab an iterator from [`values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values) and then call the `next()` method (original solution from [StackOverflow](https://stackoverflow.com/a/32539929/9599137)):

```js
const numbers = new Set()
  .add(1)
  .add(2)
  .add(3);

const firstElement = numbers.values().next().value;
// => firstElement: 1
```

2) Checking if an element is a member of the collection:

```js
const numbers = new Set()
  .add(1)
  .add(2)
  .add(3);

numbers.has(1);
// => true

numbers.has(4);
// => false
```

3) Removing a single element:

Just like the `delete()` method of `Map`, the returned boolean value indicates if the operation was successful:

```js
const numbers = new Set()
  .add(1)
  .add(2)
  .add(3);

numbers.delete(1);
// => true

numbers.delete(4);
// => false
```

4) Removing all elements:

```js
const numbers = new Set()
  .add(1)
  .add(2)
  .add(3);

numbers.clear();
// => numbers: {}
```

### Properties

```js
const numbers = new Set()
  .add(1)
  .add(2)
  .add(3);

console.log(numbers.size);
// => 3
```

### Tips & Tricks

1) Removing duplicated elements from an Array:

```js
const arr = [1, 1, 2, 3, 4, 4, 5];

const filteredArr = [...new Set(arr)];
// => filteredArr: [1, 2, 3, 4, 5]
```

2) Iteration:

```js
const numbers = new Set()
  .add(1)
  .add(2)
  .add(3);

for (const number of numbers) {
  console.log(number);
}
// => 1
// => 2
// => 3
```

3) As with `Map` keys, `NaN` elements are considered to be equal:

```js
const nanSet = new Set([NaN, NaN, NaN, NaN]);

console.log(nanSet.size);
// => 1
```

But objects are always different:

```js
const objSet = new Set([{}, {}, {}]);

console.log(objSet.size);
// => 3
```

4) Mapping and filtering

```js
const numbers = new Set()
  .add(1)
  .add(2)
  .add(3);

const mappedNumbers = new Set([...numbers].map(number => number * 2));
// => mappedNumbers: {2, 4, 6}
```

```js
const numbers = new Set()
  .add(1)
  .add(2)
  .add(3);

const filteredNumbers = new Set([...numbers].filter(number => number > 2));
// => filteredNumbers: {3}
```

5) Union

```js
const a = new Set(["foo", "bar"]);
const b = new Set(["foo", "buzz"]);

const union = new Set([...a, ...b]);
// => union: {"foo", "bar", "buzz"}
```

6) Intersection

```js
const a = new Set(["foo", "bar"]);
const b = new Set(["foo", "buzz"]);

const intersection = new Set([...a].filter(val => b.has(val)));
// => intersection: {"foo"}
```

7) Difference

```js
const a = new Set(["foo", "bar"]);
const b = new Set(["foo", "buzz"]);

const difference = new Set([...a].filter(val => !b.has(val)));
// => difference: {"bar"}
```

---

I hope you found the article helpful! Until next time!

*Psst. Would you like to add something? I'd like to hear about it. Just leave a comment or drop me a message on [Twitter](https://twitter.com/zsgomori).*