A plain object works as a key-value store for simple cases, but Map and Set solve specific limitations objects have.
const map = new Map();
map.set("a", 1);
map.set({ id: 1 }, "object key"); // Map allows ANY value as a key, not just strings/symbols
map.get("a"); // 1
map.has("a"); // true
map.size; // 2 — a real property, not a method call like Object.keys(obj).length
for (const [key, value] of map) { // Map is directly iterable, in INSERTION order
console.log(key, value);
}
| Plain object | Map | |
|---|---|---|
| Key types | String or Symbol only (numbers get coerced to strings) | Any value, including objects and functions |
| Size | Object.keys(obj).length | .size, a direct property |
| Iteration order | Insertion order for string keys, but integer-like keys are always sorted numerically first | Always strict insertion order |
| Iterable directly | No — needs Object.entries() first | Yes — for...of works directly |
| Keys colliding with inherited names | Possible — a key like "constructor" or "hasOwnProperty" shares a name with something on Object.prototype, which can confuse a naive for...in loop or a check like obj.hasOwnProperty if it was itself overwritten | Not possible — a Map's keys are just data, with no prototype chain involved |
Set stores unique values (no duplicates, checked with the same SameValueZero algorithm .includes() uses) and is the standard tool for de-duplicating an array:
const numbers = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(numbers)]; // [1, 2, 3] — spread a Set back into an array