Choosing Between Map and Plain Object for Key‑Value Storage in JavaScript
Decide when to use JavaScript Map versus a plain Object for key‑value storage by comparing key types, order guarantees, size introspection, performance, and JSON support.
24 Jul 2025, 00:58 UTC

Decision: Map or Plain Object for key‑value storage
When you need to store a collection of key‑value pairs in JavaScript, you must decide whether to use the built‑in Map type or a plain Object. The decision hinges on the types of keys you need, whether insertion order matters, how you will serialize the data, and performance characteristics for your workload.
Comparison table
| Feature | Map | Plain Object |
|---|---|---|
| Allowed key types | Any value (including objects, functions, NaN) | Only strings or Symbols; other values are coerced to strings |
| Insertion order guarantee | Yes – iteration follows insertion order | Yes in ES2015+ engines for string keys; older engines may reorder |
| Size property | map.size returns number of entries | No native size; must use Object.keys(obj).length |
| Built‑in mutation methods | set, get, has, delete, clear | Property assignment/deletion (obj[key]=value, delete obj[key]) |
| Performance – frequent adds/deletes | Generally faster due to optimized hash table | Can degrade after many deletions because of hidden class changes |
| Performance – read‑heavy lookups with string keys | Comparable; slight overhead vs. Object | Often marginally faster for simple string‑key lookups |
| Memory overhead per entry | Slightly higher (internal struct) | Lower for small, static tables |
| JSON serialization | Not natively supported; requires manual conversion | Native support via JSON.stringify |
| Prototype collision safety | Safe – no prototype lookup on keys | Unsafe unless created with Object.create(null); otherwise keys like __proto__ can clash |
Trade‑offs explained
- Key flexibility: If you need to use non‑string keys (e.g., object instances as keys),
Mapis the only safe choice. Plain objects will coerce those keys to the string "[object Object]", causing collisions. - Order sensitivity: When iteration order must match insertion order (e.g., generating a report in the sequence items were added), rely on
Map. Plain objects guarantee order only in modern engines and only for string keys. - Size introspection: Frequent checks of the collection size are clearer with
map.sizethan computingObject.keys(obj).length. - Serialization needs: If the data must be sent to a server or stored as JSON without extra transformation, a plain object is preferable. For
Map, you must convert to/from arrays or entries (Array.from(map)orObject.fromEntries(map)). - Performance profile: Benchmarks show
Mapmaintains steady insertion/deletion latency even with large, dynamic collections, whereas plain objects can show increased latency after many deletions due to hidden‑class transitions. For read‑heavy workloads with stable string keys, the difference is usually negligible. - Safety from prototype pollution: Using
Object.create(null)eliminates prototype‑property collisions, but adds a small cognitive overhead.Mapavoids this concern entirely.
Concrete implementation and validation
The following snippet demonstrates a scenario where Map is advantageous: storing metadata keyed by DOM nodes. It also includes a lightweight validation you can run in a browser console or Node.js to verify order preservation and size reporting.
// Example: attach metadata to DOM nodes using Map
const metaMap = new Map();
function storeMeta(node, data) {
metaMap.set(node, data); // node object used directly as key
}
function getMeta(node) {
return metaMap.get(node);
}
// Simulate adding three nodes
const a = document.createElement('div');
const b = document.createElement('span');
const c = document.createElement('p');
storeMeta(a, {id: 1});
storeMeta(b, {id: 2});
storeMeta(c, {id: 3});
// Validation: check size and iteration order
console.log('Map size:', metaMap.size); // Expected: 3
console.log('Iteration order:');
for (const [node, data] of metaMap) {
console.log(node.tagName, data);
}
// Compare with plain object (showing key coercion issue)
const metaObj = Object.create(null); // avoid prototype collisions
metaObj[a] = {id: 1}; // a is coerced to "[object Object]"
metaObj[b] = {id: 2}; // overwrites previous entry!
console.log('Object keys:', Object.keys(metaObj)); // Only one key
console.log('Object size via keys length:', Object.keys(metaObj).length); // 1
Where to run: Paste the code into the browser’s developer tools console while on any page, or execute it in a Node.js environment with a minimal DOM mock (e.g., using jsdom) if you need to test outside the browser.
Permissions: No special privileges are required; the snippet only creates local variables and reads/writes to the DOM if present.
Risks: The example does not alter persistent state; it only creates temporary objects. If you run it in a page that already uses the variable names metaMap, metaObj, or the node variables, you may overwrite existing values—choose unique names in practice.
Limitations: Map cannot be directly serialized with JSON.stringify. If you need JSON output, convert first:
const json = JSON.stringify(Array.from(metaMap));
// To restore:
const restoredMap = new Map(JSON.parse(json));
Practical way to check the result: After running the snippet, verify that metaMap.size equals the number of distinct nodes you added, and that the for…of loop logs the nodes in the exact order they were inserted (div, ``, p). In contrast, the plain‑object example will show a size of 1 and the iteration order (if you used for…in) will be undefined because only one key remains.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.