Maps and Sets
Keyed collections that accept any value as a key (Map) and deduplicated collections (Set) - when to reach for them instead of plain objects and arrays.
Map is a keyed collection where keys can be any value, not just strings. Set is a collection of unique values where duplicates are ignored automatically. Plain objects coerce all keys to strings; arrays don't deduplicate. Both are built into modern JavaScript.
Map works like a dictionary. You add entries with .set(key, value), retrieve them with .get(key), and check for a key with .has(key). Iteration follows insertion order, which plain objects don't guarantee on all runtimes.
const scores = new Map();
scores.set("alice", 95);
scores.set("bob", 87);
scores.set("carol", 92);
console.log(scores.get("alice")); // 95
console.log(scores.has("bob")); // true
console.log(scores.has("dave")); // false
console.log(scores.size); // 3
// Iteration order matches insertion order
for (const [name, score] of scores) {
console.log(`${name}: ${score}`);
}
// alice: 95
// bob: 87
// carol: 92Map accepts any value as a key, including objects, functions, and even NaN. A plain object coerces every key to a string, so using an object as a key silently collapses all such keys to "[object Object]".
const userCache = new Map();
const user1 = { id: 1, name: "Alice" };
const user2 = { id: 2, name: "Bob" };
userCache.set(user1, { lastSeen: "2026-04-27" });
userCache.set(user2, { lastSeen: "2026-04-26" });
console.log(userCache.get(user1)); // { lastSeen: "2026-04-27" }
console.log(userCache.get(user2)); // { lastSeen: "2026-04-26" }
// The same attempt on a plain object
const plainObj = {};
plainObj[user1] = "alice data";
plainObj[user2] = "bob data";
// Both keys stringify to "[object Object]" and clobber each other
console.log(Object.keys(plainObj)); // ["[object Object]"]
console.log(plainObj[user1]); // "bob data" - wrongSet stores unique values in insertion order. Adding a duplicate is silently ignored. Spreading a Set back into an array ([...new Set(arr)]) is the shortest idiom for deduplicating.
const tags = new Set();
tags.add("javascript");
tags.add("web");
tags.add("javascript"); // duplicate - ignored
tags.add("node");
console.log(tags.size); // 3
console.log(tags.has("javascript")); // true
console.log(tags.has("python")); // false
tags.delete("web");
console.log([...tags]); // ["javascript", "node"]
// One-liner dedup of an array
const nums = [1, 2, 3, 2, 1, 4];
const unique = [...new Set(nums)];
console.log(unique); // [1, 2, 3, 4]In production
Reach for Map whenever keys aren't string literals or insertion order matters, and reach for Set whenever you'd otherwise write arr.includes(x) inside a loop (a linear scan each call). A Set.has() check is O(1). Plain objects also walk the prototype chain, so keys like toString or constructor collide with inherited properties - Map has no such issue.
Enjoyed this? Get more essays on software craft delivered to your inbox.
Subscribe free