JavaScript Map and Object as Hash Tables
JavaScript Map and Object as Hash Tables
In JavaScript, managing key-value pairs is a fundamental task, and the language provides two primary built-in constructs for this purpose: the Map object and plain Objects ({}). Both can function conceptually as hash tables, allowing you to store and retrieve values using unique keys. However, they are designed for different use cases and have distinct characteristics.
Understanding when to use a Map versus a plain Object is crucial for writing efficient, robust, and idiomatic JavaScript code.
1. The Core Idea: Key-Value Storage
At their heart, both Map and Object allow you to associate a value with a key. They both leverage internal hashing mechanisms to provide fast, average O(1) time complexity for basic operations like adding, retrieving, and deleting entries.
Object: You define properties on an object using either dot notation (obj.key = value) or bracket notation (obj['key'] = value).Map: You use the.set(key, value)method to store entries.
Despite this fundamental similarity, their underlying behaviors and design philosophies diverge significantly.
2. Map vs. Plain Object: A Detailed Comparison
Here's a breakdown of the key differences that impact their suitability as hash tables:
2.1 Key Types
-
Map:- Allows any data type (primitives like numbers, booleans,
NaN,null,undefined, as well as objects, arrays, and functions) to be used as keys. Keys are compared by identity (for objects) or value (for primitives). - This is a major advantage when you need to use complex objects as keys.
const myObjectKey = { id: 1 }; const myArrayKey = [1, 2]; const myFuncKey = () => {}; const map = new Map(); map.set(myObjectKey, 'value for object'); map.set(myArrayKey, 'value for array'); map.set(myFuncKey, 'value for function'); console.log(map.get(myObjectKey)); // 'value for object' - Allows any data type (primitives like numbers, booleans,
-
Plain
Object:- Keys are implicitly converted to strings or Symbols. If you use a non-string/non-symbol as a key, it will be coerced to a string. This can lead to unexpected behavior and unintended key collisions.
const obj = {}; const key1 = { a: 1 }; const key2 = { b: 2 }; obj[key1] = 'first'; // key1 becomes "[object Object]" obj[key2] = 'second'; // key2 also becomes "[object Object]", overwriting 'first' console.log(obj[key1]); // 'second' console.log(obj); // { '[object Object]': 'second' }
2.2 Order of Iteration
-
Map:- Guarantees that elements are iterated in insertion order. When you loop over a
Map, you will retrieve entries in the sequence they were added. This is a consistent and reliable feature.
- Guarantees that elements are iterated in insertion order. When you loop over a
-
Plain
Object:- The iteration order of properties on plain objects has evolved in JavaScript. Since ES2015, properties with string keys are generally iterated in insertion order, except for integer-like string keys (e.g.,
"1","10") which are iterated numerically first. This makes the order less straightforward thanMap's strict insertion order.
- The iteration order of properties on plain objects has evolved in JavaScript. Since ES2015, properties with string keys are generally iterated in insertion order, except for integer-like string keys (e.g.,
2.3 Size Property
-
Map:- Has a direct
.sizeproperty that returns the number of key-value pairs inO(1)(constant) time.
const map = new Map([['a', 1], ['b', 2]]); console.log(map.size); // 2 - Has a direct
-
Plain
Object:- To get the number of properties, you typically use
Object.keys(obj).length,Object.values(obj).length, orObject.entries(obj).length. These operations involve iterating over all properties, making themO(N)(linear) time complexity.
const obj = { a: 1, b: 2 }; console.log(Object.keys(obj).length); // 2 - To get the number of properties, you typically use
2.4 Inherited Properties / Prototype Chain
-
Map:- Does not have inherited properties from its prototype that could conflict with your keys. It's a "clean slate" for key-value storage.
-
Plain
Object:- Inherits properties and methods from
Object.prototype(e.g.,toString,hasOwnProperty,constructor). If you use one of these as a key, it can lead to unexpected behavior or collisions unless carefully managed (e.g., by usingObject.create(null)to create an object without a prototype, orobj.hasOwnProperty(key)checks).
- Inherits properties and methods from
2.5 Performance Characteristics
-
Map:- Generally optimized for frequent additions, deletions, and very large datasets when used as a true hash table. Its internal implementation is geared towards consistent
O(1)average-case performance for all key types.
- Generally optimized for frequent additions, deletions, and very large datasets when used as a true hash table. Its internal implementation is geared towards consistent
-
Plain
Object:- Highly optimized for their intended use as general-purpose objects (like records or structs with known properties). However, their performance can be less predictable or efficient than
Mapwhen used as highly dynamic hash maps, especially with non-string keys or very large numbers of entries that change frequently.
- Highly optimized for their intended use as general-purpose objects (like records or structs with known properties). However, their performance can be less predictable or efficient than
2.6 JSON Serialization
-
Map:- Does not directly serialize to JSON using
JSON.stringify(). You would need to convert it to an array or plain object first.
const map = new Map([['a', 1]]); console.log(JSON.stringify(map)); // {} (empty object, not serialized) - Does not directly serialize to JSON using
-
Plain
Object:- Directly JSON serializable, making them convenient for data interchange.
const obj = { a: 1 }; console.log(JSON.stringify(obj)); // '{"a":1}'
3. When to Choose Which
The choice between Map and Object as a hash table equivalent largely depends on your specific needs:
Use Map when:
- You need to use non-string or non-symbol values as keys (e.g., DOM elements, other objects, arrays, functions,
NaN). - The insertion order of elements is important for iteration.
- You anticipate frequent additions and deletions of key-value pairs.
- You need a reliable,
O(1)way to get the number of entries (.size). - You want to avoid potential property name conflicts with
Object.prototypemethods.
Use Plain Objects ({}) when:
- Your keys will always be strings or Symbols.
- You are primarily representing structured data where properties are known beforehand (acting like a record, struct, or configuration object).
- You prefer the concise object literal syntax (
{ key: value }) for creation. - You need direct JSON serialization of your data.
- You need to use
thisbinding or work with class methods that rely on the prototype chain.
Key Takeaway: While both
MapandObjectprovide key-value storage in JavaScript,Mapis the purpose-built solution for general-purpose hash map functionality. It offers greater flexibility in key types, guaranteed insertion order, and more predictable performance for dynamic data. PlainObjects, however, remain excellent for structured data with string/symbol keys and for scenarios where JSON serialization or simple literal creation is a priority.

