DEV Community

Duncan McArdle
Duncan McArdle

Posted on • Originally published at duncan-mcardle.Medium

2 1

Maps & Sets in JavaScript

In JavaScript, objects are wonderfully versatile, and arrays are wonderfully fast. But a couple more items sit in the middle of these two; the lesser know Map and Set.

Designed to make data access faster and simpler, you’ll be hard-pressed to find a coding challenge site that doesn’t involve at least one of these (typically Map) on more than a few of their challenges.

Map and Set share some things, so let’s kick things off with Map.

Map

A Map is a key-value store (much like an object), that will allow you you to use just about anything you want as a key, maintains insertion order, has a size property, and rejects duplicate keys. Now that’s a lot of features right there, so let’s look at a basic example first:

let contacts = new Map();
contacts.set('Duncan', '1234');
contacts.set('Boris', '2345');
contacts.set('Theresa', '3456');
contacts.set('David', '4567');
console.log(contacts); // Map { 'Duncan' => '1234', 'Boris' => '2345', 'Theresa' => '3456', 'David' => '4567' }
console.log(contacts.get('Duncan')); // 1234

Straight away you can begin to see the performance advantages of a Map. No more looping through massive data-sets to find what you need, just call Map.get() and you’re done! The classic example for a good Map is often a phone-book, and that makes sense, because you can imagine how efficient it is to call upon any random contact from a phone-book using a Map, which knows each and every entry in there by heart.

Keys in a Map

But Map is also incredibly versatile. Unlike arrays, it’ll let you use just about whatever you want as a key, whether it be strings, integers, objects, or even functions!

let contacts = new Map();
const someObject = {
prop1: 'value1',
};
function someFunction() {}
contacts.set('Duncan', '1234');
contacts.set(2, '2345');
contacts.set(someObject, '3456');
contacts.set(someFunction, '4567');
console.log(contacts.get('Duncan')); // 1234
console.log(contacts.get(2)); // 2345
console.log(contacts.get(someObject)); // 3456
console.log(contacts.get(someFunction)); // 4567

Looping through a Map

A Map will also retain its insertion order, which is incredibly important when you want to know what came first. Now of course, Map isn’t an array, so you can’t call mapName[0] to get the first entry (as 0 is a valid key for a Map!), so you might be wondering why insertion order matters?

Well, Map also come with a variety of methods you’ll find familiar from my post on for loops, such as Map.values() , Map.keys() , Map.entries() and the infamous forEach() , all of which benefit from the original data being in the order it was inserted.

let contacts = new Map();
contacts.set('Duncan', '1234');
contacts.set('Boris', '2345');
contacts.set('Theresa', '3456');
contacts.set('David', '4567');
console.log(contacts); // Map { 'Duncan' => '1234', 'Boris' => '2345', 'Theresa' => '3456', 'David' => '4567' }
// for...of
for (contact of contacts.entries()) {
console.log(contact);
// Outputs:
// [ 'Duncan', '1234' ]
// [ 'Boris', '2345' ]
// [ 'Theresa', '3456' ]
// [ 'David', '4567' ]
}
// forEach
contacts.forEach((value, key) => {
console.log(key, value);
// Outputs:
// Duncan 1234
// Boris 2345
// Theresa 3456
// David 4567
});
// keys()
Array.from(contacts.keys()).forEach((key) => {
console.log(key, contacts.get(key));
// Outputs:
// Duncan 1234
// Boris 2345
// Theresa 3456
// David 4567
});

Duplication in a Map

Finally it’s worth pointing out another helpful Map function: Map.has() . This is a simple utility for checking whether a given key already exists in a Map, and can therefore be used to avoid overwriting existing data (which Map will absolutely allow!):

let contacts = new Map();
contacts.set('Duncan', '1234');
contacts.set('Duncan', '2345'); // Will overwrite
if (!contacts.has('Duncan')) {
contacts.set('Duncan', '3456'); // Won't be called
}
console.log(contacts); // Map { 'Duncan' => '2345' }

Set

Now that we’ve discussed Map , Set is actually super-simple. Think of Set as being a Map without the keys. So all values, no keys. Simple!

let someSet = new Set();
const someObject = { val: 3, name: 'Third value' };
someSet.add('First value');
someSet.add(2);
someSet.add(someObject);
console.log(someSet); // Set { 'First value', 2, { val: 3, name: 'Third value' } }

Duplication in a Set

The beauty of this is that it’s a very straightforward interface for dumping data that will retain its insertion order. But one really powerful aspect is that it will not allow data to be non-unique. This means that if you have a 10,000 item data set, and you insert it all into a Set, what you’ll be left with will all be unique. In addition, Set does (like Map) implement .has() , but this time around you’ll be checking if the Set has a value, rather than checking if a Map has a key.

let someSet = new Set();
someSet.add('First value');
someSet.add('First value'); // Fails silently
console.log(someSet); // Set { 'First value' }
if (!someSet.has('First value')) {
someSet.add('First value'); // Won't be called
}

If you’re suddenly thinking back to the last time you did a coding challenge, and realising this would have been super helpful, don’t worry, we’ve all been there.

Looping through a Set

Just like with Map, Set has a number of helpful methods for iteration. You can use Set.keys() , Set.values() and Set.entries() , as well as the classic Set.forEach() . Though if you’re sitting there thinking “Wait, what keys, and what entries?”, you’d be right. In order to keep Map and Set close together in terms of spec, these additional functions have been provided, but in reality, they all return the same thing (or in some cases, two versions of the same thing!):

let contacts = new Set();
contacts.add('Duncan');
contacts.add('Boris');
contacts.add('Theresa');
contacts.add('David');
console.log(contacts); // Set { 'Duncan', 'Boris', 'Theresa', 'David' }
console.log(contacts.keys()); // [Set Iterator] { 'Duncan', 'Boris', 'Theresa', 'David' }
console.log(contacts.entries()); // [Set Entries] { [ 'Duncan', 'Duncan' ], [ 'Boris', 'Boris' ], [ 'Theresa', 'Theresa' ], [ 'David', 'David' ] }
console.log(contacts.values()); // [Set Entries] { [ 'Duncan', 'Duncan' ], [ 'Boris', 'Boris' ], [ 'Theresa', 'Theresa' ], [ 'David', 'David' ] }
// for...of
for (contact of contacts.entries()) {
console.log(contact);
// Outputs:
// [ 'Duncan', 'Duncan' ]
// [ 'Boris', 'Boris' ]
// [ 'Theresa', 'Theresa' ]
// [ 'David', 'David' ]
}
// forEach
contacts.forEach((value) => {
console.log(value);
// Outputs:
// Duncan
// Boris
// Theresa
// David
});

Communal functionality ( .clear() and .delete() )

In addition to the communal functionality and similarities already listed, both Map and Set also implement clear() (which will completely empty out the contents) and delete() (which will remove a specific entry (by key for Map and by value for Set)).

// Map
let contacts = new Map();
contacts.set('Duncan', '1234');
contacts.set('Boris', '2345');
console.log(contacts); // Map { 'Duncan' => '1234', 'Boris' => '2345' }
contacts.delete('Boris');
console.log(contacts); // Map { 'Duncan' => '1234' }
contacts.clear();
console.log(contacts); // Map {}
// Set
let people = new Set();
people.add('Duncan');
people.add('Boris');
console.log(people); // Set { 'Duncan', 'Boris' }
people.delete('Boris');
console.log(people); // Set { 'Duncan' }
people.clear();
console.log(people); // Set {}

When to use Map & Set

Now that you know how to use these wonderful data structures, you might be wondering when to use them. Well the simple answer is: for data-sets (as the name suggests).

For Map , the power of .get() is truly incredible. Being able to dump massive amounts of data into a Map and then be able to call upon the entry you need without looping is incredibly useful, and comes without the overhead and complexities of the classic Object.

For Set, my personal favourite feature (and one I alluded to earlier) is being able to guarantee a unique set of data. This is logic that would normally be difficult and time consuming, but Set just… does it!

As with all of these modern constructs, it’s all about using the right tool for the job, so I hope now that you’ve read this article, you’ll feel just a little bit better equipped to make that decision.

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more