Map vs Object in JavaScript

Map vs Object in JavaScript

I just discovered and, after losing several hours of my day, found this feature entry:

Map: Map objects are simple key/value maps.

That confused me. Regular JavaScript objects are dictionaries, so how is a Map different from a dictionary? Conceptually, they’re identical (according to What is the difference between a Map and a Dictionary?)
The documentation chromestatus references doesn’t help either:

Map objects are collections of key/value pairs where both the keys and values may be arbitrary ECMAScript language values. A distinct key value may only occur in one key/value pair within the Map’s collection. Distinct key values as discriminated using the a comparision algorithm that is selected when the Map is created.
A Map object can iterate its elements in insertion order. Map object must be implemented using either hash tables or other mechanisms that, on average, provide access times that are sublinear on the number of elements in the collection. The data structures used in this Map objects specification is only intended to describe the required observable semantics of Map objects. It is not intended to be a viable implementation model.

…still sounds like an object to me, so clearly I’ve missed something.
Why is JavaScript gaining a (well-supported) Map object? What does it do?


Solution 1:

According to mozilla:

A Map object can iterate its elements in insertion order – a for..of loop will return an array of [key, value] for each iteration.


Objects are similar to Maps in that both let you set keys to values,
retrieve those values, delete keys, and detect whether something is
stored at a key. Because of this, Objects have been used as Maps
historically; however, there are important differences between Objects
and Maps that make using a Map better.

An Object has a prototype, so there are default keys in the map.
However, this can be bypassed using map = Object.create(null). The
keys of an Object are Strings, where they can be any value for a Map.
You can get the size of a Map easily while you have to manually keep
track of size for an Object.

Use maps over objects when keys are unknown until run time, and when
all keys are the same type and all values are the same type.

Use objects when there is logic that operates on individual elements.

The iterability-in-order is a feature that has long been wanted by developers, in part because it ensures the same performance in all browsers. So to me that’s a big one.

The myMap.has(key) method will be especially handy, and also the myMap.size property.

Solution 2:

The key difference is that Objects only support string keys where as Maps support more or less any key type.

If I do obj[123] = true and then Object.keys(obj) then I will get [“123”] rather than [123]. A Map would preserve the type of the key and return [123] which is great. Maps also allow you to use Objects as keys. Traditionally to do this you would have to give objects some kind of unique identifier to hash them (I don’t think I’ve ever seen anything like getObjectId in JS as part of the standard). Maps also guarantee preservation of order so are all round better for preservation and can sometimes save you needing to do a few sorts.

Between maps and objects in practice very are several pros and cons. Objects gain both advantages and disadvantages being very tightly integrated into the core of JS which sets them apart from significantly Map beyond the difference in key support.

An immediate advantage is that you have syntactical support for Objects making it easy to access elements. You also have direct support for it with JSON. When used as a hash it’s annoying to get an object without any properties at all. By default if you want to use Objects as a hash table they will be polluted and you will often have to call hasOwnProperty on them when accessing properties. You can see here how by default Objects are polluted and how to create hopefully unpolluted objects for use as hashes:

    toString() { [native code] }
    toString() { [native code] }
JSON.parse('{}', (k,v) => (typeof v === 'object' && Object.setPrototypeOf(v, null) ,v)).toString

Pollution on objects is not only something that makes code more annoying, slower, etc but can also have potential consequences for security.

Related:  What's the best way (most efficient) to turn all the keys of an object to lower case?

Objects are not pure hash tables but are trying to do more. You have headaches like hasOwnProperty, not being able to get the length easily (Object.keys(obj).length) and so on. Objects are not meant to purely be used as hash maps but as dynamic extendible Objects as well and so when you use them as pure hash tables problems arise.

Comparison/List of various common operations:

       var o = {};
       var o = Object.create(null);
       o.key = 1;
       o.key += 10;
       for(let k in o) o[k]++;
       var sum = 0;
       for(let v of Object.values(m)) sum += v;
       if('key' in o);
       var m = new Map();
       m.set('key', 1);
       m.set('key', m.get('key') + 10);
       m.foreach((k, v) => m.set(k, m.get(k) + 1));
       for(let k of m.keys()) m.set(k, m.get(k) + 1);
       var sum = 0;
       for(let v of m.values()) sum += v;

There are a few other options, approached, methodologies, etc with varying ups and downs (performance, terse, portable, extendable, etc). Objects are a bit strange being core to the language so you have a lot of static methods for working with them.

Besides the advantage of Maps preserving key types as well as being able to support things like objects as keys they are isolated from the side effects that objects much have. A Map is a pure hash, there’s no confusion about trying to be an object at the same time. Maps can also be easily extended with proxy functions. Object’s currently have a Proxy class however performance and memory usage is grim, in fact creating your own proxy that looks like Map for Objects currently performs better than Proxy.

A substantial disadvantage for Maps is that they are not supported with JSON directly. Parsing is possible but has several hangups:

JSON.parse(str, (k,v) => {
    if(typeof v !== 'object') return v;
    let m = new Map();
    for(k in v) m.set(k, v[k]);
    return m;

The above will introduce a serious performance hit and will also not support any string keys. JSON encoding is even more difficult and problematic (this is one of many approaches):

// An alternative to this it to use a replacer in JSON.stringify.
Map.prototype.toJSON = function() {
    return JSON.stringify({
        keys: Array.from(this.keys()),
        values: Array.from(this.values())

This is not so bad if you’re purely using Maps but will have problems when you are mixing types or using non-scalar values as keys (not that JSON is perfect with that kind of issue as it is, IE circular object reference). I haven’t tested it but chances are that it will severely hurt performance compared to stringify.

Other scripting languages often don’t have such problems as they have explicit non-scalar types for Map, Object and Array. Web development is often a pain with non-scalar types where you have to deal with things like PHP merges Array/Map with Object using A/M for properties and JS merges Map/Object with Array extending M/O. Merging complex types is the devil’s bane of high level scripting languages.

So far these are largely issues around implementation but performance for basic operations is important as well. Performance is also complex because it depends on engine and usage. Take my tests with a grain of salt as I cannot rule out any mistake (I have to rush this). You should also run your own tests to confirm as mine examine only very specific simple scenarios to give a rough indication only. According to tests in Chrome for very large objects/maps the performance for objects is worse because of delete which is apparently somehow proportionate to the number of keys rather than O(1):

Related:  Using spread syntax and new Set() with typescript
Object Set Took: 146
Object Update Took: 7
Object Get Took: 4
Object Delete Took: 8239
Map Set Took: 80
Map Update Took: 51
Map Get Took: 40
Map Delete Took: 2

Chrome clearly has a strong advantage with getting and updating but the delete performance is horrific. Maps use a tiny amount more memory in this case (overhead) but with only one Object/Map being tested with millions of keys the impact of overhead for maps is not expressed well. With memory management objects also do seem to free earlier if I am reading the profile correctly which might be one benefit in favour of objects.

In FireFox for this particular benchmark it is a different story:

Object Set Took: 435
Object Update Took: 126
Object Get Took: 50
Object Delete Took: 2
Map Set Took: 63
Map Update Took: 59
Map Get Took: 33
Map Delete Took: 1

I should immediately point out that in this particular benchmark deleting from objects in FireFox is not causing any problems, however in other benchmarks it has caused problems especially when there are many keys just as in Chrome. Maps are clearly superior in FireFox for large collections.

However this is not the end of the story, what about many small objects or maps? I have done a quick benchmark of this but not an exhaustive one (setting/getting) of which performs best with a small number of keys in the above operations. This test is more about memory and initialisation.

Map Create: 69    // new Map
Object Create: 34 // {}

Again these figures vary but basically Object has a good lead. In some cases the lead for Objects over maps is extreme (~10 times better) but on average it was around 2-3 times better. It seems extreme performance spikes can work both ways. I only tested this in Chrome and creation to profile memory usage and overhead. I was quite surprised to see that in Chrome it appears that Maps with one key use around 30 times more memory than Objects with one key.

For testing many small objects with all the above operations (4 keys):

Chrome Object Took: 61
Chrome Map Took: 67
FireFox Object Took: 54
FireFox Map Took: 139

In terms of memory allocation these behaved the same in terms of freeing/GC but Map used 5 times more memory. This test used 4 keys where as in the last test I only set one key so this would explain the reduction in memory overhead. I ran this test a few times and Map/Object are more or less neck and neck overall for Chrome in terms of overall speed. In FireFox for small Objects there is a definite performance advantage over maps overall.

This of course doesn’t include the individual options which could vary wildly. I would not advice micro-optimising with these figures. What you can get out of this is that as a rule of thumb, consider Maps more strongly for very large key value stores and objects for small key value stores.

Beyond that the best strategy with these two it to implement it and just make it work first. When profiling it is important to keep in mind that sometimes things that you wouldn’t think would be slow when looking at them can be incredibly slow because of engine quirks as seen with the object key deletion case.

Solution 3:

I don’t think the following points have been mentioned in the answers so far, and I thought they’d be worth mentioning.

Maps can be bigger

In chrome I can get 16.7 million key/value pairs with Map vs. 11.1 million with a regular object. Almost exactly 50% more pairs with a Map. They both take up about 2GB of memory before they crash, and so I think may be to do with memory limiting by chrome (Edit: Yep, try filling 2 Maps and you only get to 8.3 million pairs each before it crashes). You can test it yourself with this code (run them separately and not at the same time, obviously):

var m = new Map();
var i = 0;
while(1) {
    m.set(((10**30)*Math.random()).toString(36), ((10**30)*Math.random()).toString(36));
    if(i%1000 === 0) { console.log(i/1000,"thousand") }
// versus:
var m = {};
var i = 0;
while(1) {
    m[((10**30)*Math.random()).toString(36)] = ((10**30)*Math.random()).toString(36);
    if(i%1000 === 0) { console.log(i/1000,"thousand") }

Objects have some properties/keys already

This one has tripped me up before. Regular objects have toString, constructor, valueOf, hasOwnProperty, isPrototypeOf and a bunch of other pre-existing properties. This may not be a big problem for most use cases, but it has caused problems for me before.

Related:  Can you insert a line break in text when using d3.js? [duplicate]

Maps can be slower:

Due to the .get function call overhead and lack of internal optimisation, Map can be considerably slower than a plain old JavaScript object for some tasks.

Solution 4:

In addition to the other answers, I’ve found that Maps are more unwieldy and verbose to operate with than objects.

obj[key] += x
// vs.
map.set(map.get(key) + x)

This is important, because shorter code is faster to read, more directly expressive, and better kept in the programmer’s head.

Another aspect: because set() returns the map, not the value, it’s impossible to chain assignments.

foo = obj[key] = x;  // Does what you expect
foo = map.set(key, x)  // foo !== x; foo === map

Debugging maps is also more painful. Below, you can’t actually see what keys are in the map. You’d have to write code to do that.

Good luck evaluating a Map Iterator

Objects can be evaluated by any IDE:

WebStorm evaluating an object

Solution 5:

Objects can behave like dictionaries because Javascript is dynamically typed but they really aren’t meant to be.

The new Map() functionality is much nicer because it has normal get/set/has/delete methods, accepts any type for the keys instead of just strings, easier to use when iterating, and it doesn’t have edge cases with prototypes and other properties showing up. It’s also very fast and keeps getting faster as engines get better. 99% of the time you should just use a Map().

However if you’re only using string-based keys and need maximum read performance, then objects can be a better choice. The detail is that (almost all) javascript engines compile objects down to C++ classes in the background. These types are cached and reused by their “outline”, so when you make a new object with the same exact properties the engine will reuse an existing background class. The access path for properties on these classes is very optimized and much faster than the lookup of a Map().

Adding or removing a property causes the cached backing class to be re-compiled which is why using an object as a dictionary with lots of key additions and deletions is very slow, but reads and assignment of existing keys without changing the object are very fast.

So if you have a write-once read-heavy workload with string keys then use an object as a specialized high-performance dictionary, but for everything else use a Map().

Solution 6:

Additionally to being iterable in a well-defined order, and the ability to use arbitrary values as keys (except -0), maps can be useful because of the following reasons:

  • The spec enforces map operations to be sublinear on average.

    Any non-stupid implementation of object will use a hash table or similar, so property lookups will probably be constant on average. Then objects could be even faster than maps. But that is not required by the spec.

  • Objects can have nasty unexpected behaviors.

    For example, let’s say you didn’t set any foo property to a newly created object obj, so you expect to return undefined. But foo could be built-in property inherited from Object.prototype. Or you attempt to create by using an assignment, but some setter in Object.prototype runs instead of storing your value.

    Maps prevent these kind of things. Well, unless some script messes up with Map.prototype. And Object.create(null) would work too, but then you lose the simple object initializer syntax.