Limitations of objects

As we've already seen, the plain JS object is a great way for organizing our data.

However objects come with limitations, one that we've not brought attention to up until this point. Its keys have to be strings (or less frequently used, symbols). So what happens if you try to use a non string value for your object keys, such as 1, a number and true a boolean:

const nums = {
  1: 1,
  true: true,

Well we can see that both keys are actually converted to strings if we use a special Object method called object.keys.

Object.keys(nums); // => ['1', 'true']

In this result, we get both of our keys from the object, and as you see they are wrapped in quotes, indicating they are type string.

So there is implicit conversion of keys from whatever value we provide to a string. As a result, we can't have unique types held as keys on our objects.

In a number of ways, the JS object lacks flexibility and does things we wouldn't expect. But recently, with the addition of ES6 we have a new object data type called a map.

Think of maps as objects plus. It works and was meant to be used just like a normal object, as a key-value storage, but it was created to solve a lot of the inherent problems of objct. In this lesson we're going to dive into when you should use map over plain objects.

The map accepts any key type

The first is the situation we just covered--if the object’s key is not a string or symbol, JavaScript implicitly transforms it into a string.

Maps are special because keys can be any primitive type: strings, numbers, boolean, and yes, the primitive we haven't covered--symbols. Whatever type we used will be preserves and not implicitly changed to another. This is arguably map's main benefit.

So since it works just like an object, let's see how to add values to it.

Unlike the curly braces syntax used to create an object, we create a Map by saying new map.

new Map();

Like object literals, however, we can declare values on it immediately when it's created. To create these key-values pairs, we include a pair of square brackets:

new Map([]);

And then for each key-value pair, we add an additional set of brackets, that first contains the key and after a comma, it's corresponding value.

new Map(["key", "value"]);

So let's put maps to the test, and create our previous object as a map. We'll make the key for the first pair the number 1, and it's value 1. And for the second pair, the key will be the boolean true and the value true.

new Map([
  [1, 1],
  [true, true],

Note that like objects, each key-value pair needs to be separated by a comma.

And if we console log this:

  new Map([
    [1, 1],
    [true, true],

We get our created Map. So these pairs are totally valid for a map. And like most values in JS, we want to put this map in a variable. We'll call this map1:

const map1 = new Map([
  [1, 1],
  [true, true],

Now let's take a look at an alternate way of adding keys and values to a map, particularly after it has been initially created.

Say if we want to add another key-value pair to map1 later in our program, we could use a special method available on every map called .set(). It mutates our map object and the first argument is the key, and the second is the value:

map1.set("key", "value");

So let's add this string to our map to see that all primitives can be added to it as keys.

[Run code]

And then to prove that we our types are being maintained, we can run this line here, to get all of the keys. You'll understand what this line does in the next section when we cover arrays


[Run code]

And we get a number, boolean, and string. So in addition to being able to accept keys as whatever primitive we like, did you notice one other thing that map includes, based off of this result? Take a second and see if you can see it.

It might be hard to notice, but look at the order of our keys. It's exactly the same as we added them. The first two keys are in the same order as we declared then when we created the map and then the last key was added to the end when we used set.

This ordered nature of maps is not present with normal objects. Be aware that normal objects are unordered and the key and values are not arranged in the object according to when they are inserted. However, Maps do preserve insertion order. If you added pairs in a certain order, that will be maintained.

Now let's see key ordering touches on a familiar topic--in the last section, we saw the different patterns for iterating over an objects data. Let's see how to iterate over a

Since Map is a newer addition to the language and realizing that iteration is necessary sometimes for objects, a convenient function was built into Maps called that enables us to loop over their data. This is called forEach.

So to iterate over all of our map1 data, we can just say map1.forEach. And forEach is a method that accepts our own function. And most of the time, for when a method accepts a function, we use an arrow function for simplicity's sake, so our code doesn't get too cluttered.

map1.forEach(() => {});

And what does forEach do? Well it gives the function we pass to it the two pieces of data we want, naturally. For each pair in the Map, we get its value (that is the first parameter, and then its corresponding key):

forEach will call our function for each individual pair in the map. So to see each data point, we'll just log the key and value:

map1.forEach((value, key) => {
  console.log(`${key}: ${value}`);

If this is confusing for you right now, don't worry, we'll cover methods much more in depth in our arrays section.

So when we run this code, what should we expect to see? What will be first, second and third?

[Run code] We see the key with the number 1 and it's value, then the boolean key true, and last our string key.

So again, even in iteration, the order is preserved for Maps. So in one sense, Maps are more flexible due to their ability to store more key data types, but they are also more structured due to maintaining the order we impose on them.

Objects as keys

So let's dive even deeper into what map can do, that might seem somewhat strange--can you use further an entire object as a key? In fact, you can.

Let’s say we have a couple of objects, for example, a couple set of user data:

const user1 = { name: "john" };
const user2 = { name: "mary" };

And we need to store some important related data with these objects, but we don't want to attach them to the objects themselves. So for example, say we have a couple of secret keys that are associated with each user, but we want to keep them separate so the users themselves can't see them.

const secretkey1 = "asdflaksjfd";
const secretkey2 = "alsfkdjasldfj";

To solve this problem with objects isn't possible. But there’s a workaround: to make our users the keys and their related secret keys as values:

new Map([
  [user1, secretkey1],
  [user2, secretkey2],

And if call this map secretKeyMap and console.log it:

const secretKeyMap = new Map([
  [user1, secretkey1],
  [user2, secretkey2],

[Run code], we see in fact that the user objects were made as keys.

Now there are a couple of downsides to this approach that we should be aware of:

  1. That it becomes much harder now to access any of the properties off of the keys if we need them. Be aware that such an approach is best when we just need to get the value. What's incredible about this approach is that all we have to do now to get the secret key of each of the users is just to reference of each user stored in their variables.

And we do this using the opposite of the .set() method to put key-value pairs on maps, .get().

To get the secret key of the first user, we can just say:

const key = secretKeyMap.get(user1);

And if we run this--[Run code] we get our associated key. And the same will work for user2:

const secretKeyMap = new WeakMap([
  [user1, secretkey1],
  [user2, secretkey2],

const key = secretKeyMap.get(user2);

[Run code]

  1. And the second downside is that our objects can be very large and can take up a lot of memory in our application, making it slower. So when we are done using this map, we want it to be garbage collected--that is, thrown away so we can clear up more places in memory for new values.

To do so, we can use a variant of map that is optimized for garbage collection. This is called WeakMap and since it was designed for this purpose, it only accepts objects as keys.

So all we have to do is replace where we used Map with WeakMap and it still works like before:

const key = secretKeyMap.get(user2);

[Run code]

So that's all you need to know about WeakMap. Works exactly like Map, but use it for situations just like this where there's a benefit to using objects as keys.

Size of Map

Finally, a significant improvement that Map brings to data that needs to be stored as key-value pairs is that we can easily know how long it is.

You might not be aware of this, but for the normal JS object, there is no length property that tells you how many values it has.

Instead we have to use a trick involving the Object.keys() method we saw earlier. We have to use Object.keys in to an array of its key values and then use Object.keys().length to get how many data points it has:

  1. Map’s size You cannot easily determine the number of properties in a plain object.

One workaround is to use a helper function like Object.keys():

const user = {
  name: "john",
  verified: true,

console.log(Object.keys(user).length); // => 2

The map provides a much more convenient alternative. But before I show you that, take a minute and convert this user object that we just made into a map...

So for our map, we can either put the key value pairs in it immediately within square brackets, or create and use the set method to dynamically add them. I'll take the first approach but you can take either:

new Map([
  ["name", "john"],
  ["verified", true],

And remember that since our Object keys are strings, name and verified should be explicitly written as strings with single / double quotes. And I'll store the created map in a variable called userMap.

const userMap = new Map([
  ["name", "john"],
  ["verified", true],

And now, all we have to do to get the number of key value pairs is use another built-in property to Maps--.size. So if we console log that:


[Run code]

We see that has 2 values. So again, if you have data where you need to easily access the number of values that exist in it, you won't find a better data structure than map.


So in review, while we will still rely heavily on JavaScript objects do the job of holding structured data, they have some clear limitations:

  • Only strings or symbols can be used as keys
  • Own object properties might collide with property keys inherited from the prototype (e.g. toString, constructor, etc).
  • Objects cannot be used as keys
  • These limitations are solved by maps. Moreover, maps provide benefits like being iterators and allowing easy size look-up.

Objects are not good for information that’s continually updated, looped over, altered, or sorted. In those cases, use Map. Objects are a path to find information when you know where it will be.

So use maps with a purpose. Think of maps and objects like let and const. Maps don't replace objects, they just have their specific use cases. Use objects the vast majority of the time, but if your app needs one of these extra bits of functionality, use map.