JavaScript Map and Set Tutorial: how to use new built-in classes
Tags: javascript, webdev, frontend
Published: 2021-04-20 17:06:00

In the past, JavaScript was limited when it came to collections. While other languages offer lists, sets, associative maps, and dictionaries, JavaScript only offered only arrays. JavaScript  programmers had to come up with workaround for creating sets or maps, which made the code hard to maintain.

Now, with ES6, JavaScript offers **new built-in classes** for sets and maps that make programming far easier to maintain. In this tutorial, we will introduce you to map and set in detail along with code examples, use cases, and hands-on exercises. *This tutorial is ideal for JavaScript developers who want to modernize their JavaScript skills.*

**This tutorial at a glance:**

* [What is `map`?](#map)
* [How to use  `map`](#how-map)
* [What is `set`?](#set)
* [How to use `set`](#how-set)
* [Hands-on exercise with `map`](#hands-on)
* [What to learn next](#next)

<br>

<a name="map"></a>

## What is `map`?

[Prior to ES6,](https://www.educative.io/blog/javascript-versions-history) JavaScript developers used objects to map keys to values. However, using an object as a map has its limitations. For example:

* There is no foolproof way to iterate over  keys, and the `keys()` method converts fields to strings, which leads to collision of keys.
* There is no simple way to add new keys and values

ES6 introduced some new built-in [classes](https://www.educative.io/blog/javascript-oop-primer), including a **collection type called `Map`**, which can hold key-value pairs of any type. Unlike the object approach, the new Map object can remember key insertion order.

In simple terms, a JavaScript `Map` is an associative collection of distinct keys and values. Both keys and values can be any primitive or object. This is a significant data structure with many valuable uses. 



> **Note:** A `WeakMap` is similar to a Map but all keys of a `WeakMap` are objects.

To create a new `Map`, we use the following syntax:

```js

let map = new Map([iterable]);

```

Let's put this into practice with a more complex example. Below we have a Map that holds names as keys and scores as values.

```js
'use strict';

//START:DEFINE
const scores = 
  new Map([['Sara', 12], ['Bob', 11], ['Jill', 15], ['Bruce', 14]]);

scores.set('Jake', 14);

console.log(scores.size);
//END:DEFINE      
```

**Output:** 5

* The `scores` Map has been initialized with names and scores. The initial data may be any iterable with a pair of keys and values.
* We add a key and value to the Map using the `set()` method (line 7)
* To figure out how many keys are currently in the Map, we use the size property  (line 9)

> **Note:** `Map.has(key)` above will return the Boolean value to indicate if the element associated with a specified key is in the map

<br>

<a name="how-map"></a>


## How to use map
Once we know how to create maps with JavaScript, there are a lot of things we can do with them. 

### Iterate through maps

First, let's learn about iteration through maps. There are 3 methods we can use:

* `map.keys()`: returns an iterable for keys
* `map.entries()`: returns an iterable for entries `[key, value]`
* `map.values()`: returns an iterable for values


We can iterate over the collection of keys and values with the `entries()` method, which returns an iterable, so we can use the enhanced `for loop` along with destructuring. 

For example, below, we extract the name and score for each key-value pair:

```js
'use strict';

//START:DEFINE
const scores = 
  new Map([['Sara', 12], ['Bob', 11], ['Jill', 15], ['Bruce', 14]]);

scores.set('Jake', 14);
//END:DEFINE                                                                   

for(const [name, score] of scores.entries()) {
  console.log(`${name} : ${score}`);
}
```

**Output:** 
Sara : 12
Bob : 11
Jill : 15
Bruce : 14
Jake : 14

We can also use the `forEach` method, which is an internal iterator.

```js
'use strict';

//START:DEFINE
const scores = 
  new Map([['Sara', 12], ['Bob', 11], ['Jill', 15], ['Bruce', 14]]);

scores.set('Jake', 14);
//END:DEFINE                                                                   

scores.forEach((score, name) => console.log(`${name} : ${score}`));
```

**Output:**
1.88s
Sara : 12
Bob : 11
Jill : 15
Bruce : 14
Jake : 14


The first parameter that the function receives is the value for a key that appears as the second parameter. The same `forEach()` method can be used to iterate over only the values:


```js
'use strict';

//START:DEFINE
const scores = 
  new Map([['Sara', 12], ['Bob', 11], ['Jill', 15], ['Bruce', 14]]);

scores.set('Jake', 14);
//END:DEFINE                                                                   

scores.forEach(score => console.log(score));
```

**Output:** 
1.85s
12
11
15
14
14

If you receive only one parameter, it will be the value, and if you receive two parameters, then it will stand for the value and key for each key-value pair. 

<br>

### Initialize a map with an iterable object

You can also pass an iterable object to the `Map()` constructor:

```js
let userRoles = new Map([
    [sarah, 'admin'],
    [bob, 'editor'],
    [jill, 'subscriber']
]);
```

<br>

### Get an element from a map by key

We can get an elements from a map by key with the `get()` method:

But if you pass a key that is not in that map, the it will return undefined.

```js
userRoles.get(sarah); // admin
```

But if you pass a key that is not in that map, the it will return undefined.

```js
let foo = {name: 'Foo'};
userRoles.get(foo); //undefined
```

<br>

### Get the number of elements in the map

We can use the `size` property to get the number of elements in our maps.

```js
console.log(userRoles.size); // 3
```

<Br>

### Convert map keys or values to array

At times, you may want to work with an array instead of an iterable object. We can use the spread operator to convert keys for each element into a new array.

```js
var keys = [...userRoles.keys()];
console.log(keys);
```
This piece of code will convert the values of elements to an array:

```js
var roles = [...userRoles.values()];
console.log(roles);
```

<Br>

### Other important Map methods

* `clear()`: removes elements from the map object.
* `map.set(key, value)`: stores the value by the key
 * `delete(key)`: removes a specific element (as specified by the key) 
* `set(key, value)`: sets the value for the key and returns the map object. Can be chained with other methods.
 * `forEach(callback[, thisArg])`: invokes a callback for each key-value pair in insertion order. The `thisArg` parameter is optional and sets the `this` value for each callback.
* `has(key)`: returns `true` if a value associated with the key exists, otherwise, `false`.
* ` keys()`: returns a new iterator with the keys for elements in insertion order.
* `values()`: returns a new iterator object with the values for each element in insertion order.
* `map.size`: returns the current element count


<br>

<a name="set"></a>


## What is `set`?

`Set` is another new collection introduced by ES6. The `Array` class of JavaScript can work with ordered collection of data, but not so well with **unordered collections** or when values held in the collection are unique. That's why JavaScript introduced `Set`. 

A `set` is a unique collection of primitives and objects, and duplicates are not allowed. We can either create an empty set and add objects, or we can initialize a set with the contents of an iterable (like an array).

Let's explore this with an example. Below, we have a set of names with five values. One of the values is not included in the set due to duplication.

```js
'use strict';

//START:CREATE
const names = new Set(['Jack', 'Jill', 'Jake', 'Jack', 'Sara']);
//END:CREATE
  
//START:SIZE
console.log(names.size);
//END:SIZE
```
**Output:** 4

We can add elements to an existing set, like below:

```js
names.add('Matt');
```

The `add()` method returns the current `Set`, which is useful for chain operations, such as more calls to `add()` or other methods of `Set`:

```js
names.add('Kate')
  .add('Kara');
```


<br>

<a name="how-set"></a>

## How to use `set`

Once we figure out how to create sets, it's easy to work with them. First, let's look at the built-in functions for sets:

* `has()`: to check if a set has a particular element.
* `clear()`: to empty an existing set or remove an existing element using the `delete()` method.
* `keys()`: to get all the values from a Set
* `entries()`: to iterate over a Set using the enhanced for loop, like below:

```js
'use strict';

//START:CREATE
const names = new Set(['Jack', 'Jill', 'Jake', 'Jack', 'Sara']);
//END:CREATE

//START:ADD
names.add('Mike');
//END:ADD

//START:ADD2
names.add('Kate')
  .add('Kara');
//END:ADD2

console.log(names.has('Brad'));                                         

console.log(names.entries());
console.log(names.keys());
console.log(names.values());

//START:ITERATE1
for(const name of names) {
  console.log(name);
}
//END:ITERATE1
```

<br>

### `filter`/`map` with sets

Set does not yet offer methods like `filter()` and `map()`, but we can create an array from the set and use a [functional style](https://www.educative.io/blog/what-is-functional-programming-python-js-java) methods on that new array.

For example, below we use methods `filter()`, `map()`, and `forEach()` to pick only names that start with `J` and then transform them to uppercase.

```js
'use strict';

//START:CREATE
const names = new Set(['Jack', 'Jill', 'Jake', 'Jack', 'Sara']);
//END:CREATE

//START:ADD
names.add('Mike');
//END:ADD

//START:ADD2
names.add('Kate')
  .add('Kara');
//END:ADD2


//START:FILTER
[...names].filter(name => name.startsWith('J'))
  .map(name => name.toUpperCase())
  .forEach(name => console.log(name));
//END:FILTER
```

**Output:** 
JACK
JILL
JAKE


<br>

### Get the size of a Set


Use the `size` property of the Set object to return its size.

```js
let size = chars.size;
console.log(size);//  3
```

<br>

### Remove elements from a set

To remove an element from a set, use the `delete()` method. 

```js
chars.delete('f');
console.log(chars); // Set {"a", "b", "c", "d", "e"}
```

And to delete all elements of a set, use the `clear()` method:

```js
chars.clear();
console.log(chars); // Set{}
```
<br>

### Invoke a callback function on each element

To invoke a callback on every element of your set, use the `forEach()` method.

```javascript
roles.forEach(role => console.log(role.toUpperCase()));

```



<Br>

### Other important Set methods

* `new Set(iterable)`: creates a set. 
* `set.add(value)`: adds a given value and returns the set
* `set.has(value)`: returns `true` if a value exists in the set, otherwise, returns `false`.
* `set.clear()`: removes everything from a set

<br>

<a name="hands-on"></a>


## Hands-on exercise with `map`

To solidify your learning, let's do a hands-on exercise with map in JavaScript. Use `Map` to get the desired output as given below. When creating an object of `createTodo()`, it should return a map element.

| Input calls                                    	| Output           	|
|------------------------------------------------	|------------------	|
| `console.log(todo.get('learn JavaScript'));`   	| done             	|
| `console.log(todo.get('write elegant code'));` 	| work-in-progress 	|
| `console.log(todo.get('automate tests'));`     	| work-in-progress 	|
| `console.log(completedCount(todo));`           	| 1                	|

The solution to this challenge is given below.


```js
'use strict';

const createTodo = function() {
  const todo = new Map();
  todo.set('learn JavaScript', 'done');
  todo.set('write elegant code', 'work-in-progress');
  todo.set('automate tests', 'work-in-progress');
  
  return todo;
}; 

const completedCount = function(map) {
  return [...map.values()]
    .filter(value => value === 'done')
    .length;
};

const todo = createTodo(); //Returns a Map
```

### Solution Breakdown 

Start by creating a map element. The `Map` object `todo` is created on line 4 using the built-in class. You can see that the map object, `todo` is calling `Map.get()` with different keys to get their values. This means that we need to add all the keys and values.

We add the new element in `todo` with the keys and associated values. On lines 5-7, we add the new elements by setting values for the keys. 

For `completedCount()`, we  define a new function with a map object parameter. The function will return the count of tasks that are completed. So, essentially, we are filtering all the values of elements in the map object to get the elements with the value equal to `done` (see line 14).

On line 15, the length property is used to get the count of the special elements.

<br>

<a name="next"></a>


## What to learn next

Map and set are valuable additions to JavaScript, and they will make your code cleaner and easier to maintain. Now that you have a solid understanding of map and set, you're ready to tackle other features added in ES6 and beyond.

Some concepts to cover next to modernize your JavaScript are:
* Async and Await ([promises](https://www.educative.io/blog/javascript-promises-tutorial))
* Metaprogramming
* Object literals 
* Arrow functions
* and more


To get up to speed on JavaScript's features, check out Educative's course **[Rediscovering JavaScript: ES6, ES7 & ES8.](https://www.educative.io/courses/rediscovering-javascript)** This course covers the modern JavaScript features to make your code elegant, concise, expressive, and less error prone. You will start by learning the basic features of modern JavaScript, and in the second half, you will dive deep into complex features, like destructuring, literals, inheritance, modules, promises, and metaprogramming. 

By the end of this course, you will be able to add new features with minimum effort and write code more efficiently!

*Happy learning!*

<br>

### Continue reading about JavaScript
* [JavaScript Array Methods: how to use map and reduce
](https://www.educative.io/blog/javascript-array-methods-map-reduce)
* [Level up your JavaScript skills with 10 coding challenges](https://www.educative.io/blog/level-up-javascript-coding-challenges)
* [JavaScript Versions: How JavaScript has changed over the years](https://www.educative.io/blog/javascript-versions-history)