JavaScript Maps vs Sets — A Scientific, Production‑Minded Guide (2026)
Abstract
arrays/objects”—they are specialized collections with explicit mathematical and runtime guarantees:
- Map: association key → value (dictionary / symbol table)
- Set: membership of unique values (mathematical set)
If you pick the wrong one, you pay for it later: slower lookups, duplicate handling bugs, awkward iteration, or even security issues when using plain objects as maps.
This guide treats Map/Set like an engineer would: define the model, compare properties, run mental benchmarks, and choose the right data structure for production.
Table of Contents
- 1) Quick Decision Rule
- 2) Map vs Set: The Core Differences
- 3) The Scientific Model: What These Structures Are
- 4) Equality Semantics: SameValueZero (Why NaN Works)
- 5) Iteration Order and Determinism
- 6) Performance Reasoning (Big‑O Without Myths)
- 7) Map vs Object: When “Just Use {}” Breaks
- 8) Serialization: Why Map/Set Need a Plan
- 9) Production Patterns for Map
- 10) Production Patterns for Set
- 11) Pitfalls and Anti‑Patterns
- 12) TypeScript Tips
- 13) Practical Cheat Sheet
- Conclusion
1) Quick Decision Rule
Use this rule when you don’t want to think too much:
- If you need lookup by key → Map
- If you need unique membership → Set
- If you need JSON serialization with minimal ceremony and keys are strings → consider plain object (carefully), but Map is still safer.
2) Map vs Set: The Core Differences
| Characteristic | Map |
Set |
|---|---|---|
| Stores | Key → Value pairs | Unique values |
| Keys allowed | Any type (objects, functions, primitives) | N/A |
| Duplicates | Keys are unique; values can repeat | No duplicates (per equality semantics) |
| Lookup |
get(key) / has(key)
|
has(value) |
| Common use | Dictionaries, caches, indexes | De‑dup, membership, set algebra |
| Order | Insertion order preserved | Insertion order preserved |
Spanish quick ref (from your notes):
| Característica | Map | Set |
|---|---|---|
| Almacena | Pares clave‑valor | Valores únicos |
| Claves permitidas | Cualquier tipo | No aplica |
| Valores duplicados | Permitidos (por clave única) | No permitidos |
| Acceso | .get(key) |
.has(value) |
| Orden | Mantiene el orden | Mantiene el orden |
| Uso común | Asociaciones | Listas sin duplicados |
3) The Scientific Model: What These Structures Are
Map = Symbol Table (Association)
A Map<K, V> is the fundamental data structure for:
- indexes (ID → record)
- caches (key → computed result)
- adjacency lists (node → neighbors)
- frequency tables (token → count)
You are modeling relationships.
Set = Membership (Uniqueness)
A Set<T> models:
- uniqueness constraints (IDs, visited nodes)
- membership queries (is user in cohort?)
- set algebra (union/intersection/diff)
- de‑dup pipelines
You are modeling membership.
4) Equality Semantics: SameValueZero (Why NaN Works)
Both Map keys (and Set values) use SameValueZero equality:
- Like
===, butNaNequalsNaN - Also treats
0and-0as equal
Example: NaN works as a key.
const m = new Map();
m.set(NaN, "not a number");
console.log(m.get(Number("foo"))); // "not a number"
Object keys: identity, not structural equality
When you use objects as keys:
const m = new Map();
m.set({ id: 1 }, "value");
console.log(m.get({ id: 1 })); // undefined (different reference)
This is correct and intentional: objects are compared by reference identity.
5) Iteration Order and Determinism
Both Map and Set iterate in insertion order.
This matters in production because it gives you deterministic behavior for:
- stable UI rendering
- deterministic logs
- reproducible tests
const m = new Map();
m.set("b", 2);
m.set("a", 1);
console.log([...m.keys()]); // ["b","a"]
6) Performance Reasoning (Big‑O Without Myths)
What the spec implies
The spec requires implementations to provide access times that are sublinear on average. In practice, engines implement Map/Set with hash tables (amortized O(1) lookup).
Practical heuristics
-
Set#has(x)is typically faster thanarray.includes(x)for large collections. -
Map#get(k)is typically faster and cleaner thanobject[k]when keys are not strings or when you need safe iteration + size.
When performance is not the bottleneck
For small sizes (tens of items), readability and correctness dominate.
Choose the structure that matches your model.
7) Map vs Object: When “Just Use {}” Breaks
Objects historically acted like maps, but they have hazards:
7.1 Accidental keys / prototype chain
Objects inherit from Object.prototype:
const o = {};
console.log("toString" in o); // true
So user-provided keys can collide with prototype keys unless you use Object.create(null).
7.2 Security: prototype pollution / object injection
If you assign user-provided keys to a plain object, attackers may inject special keys like __proto__ (depending on usage patterns), leading to prototype pollution vulnerabilities.
Map does not have this class of problem because it has no prototype keys in its stored data.
7.3 Key type limitations
Object keys are strings or symbols. Map keys can be anything:
const fn = () => {};
const m = new Map();
m.set(fn, "handler");
7.4 Size and iteration ergonomics
-
Map#sizeis O(1) -
Object.keys(o).lengthallocates an array
8) Serialization: Why Map/Set Need a Plan
JSON.stringify() does not natively serialize Map/Set meaningfully.
Serialize Map
const m = new Map([["a", 1], ["b", 2]]);
const json = JSON.stringify([...m]); // array of entries
const restored = new Map(JSON.parse(json));
Serialize Set
const s = new Set([1, 2, 3]);
const json = JSON.stringify([...s]);
const restored = new Set(JSON.parse(json));
If you need stable, typed serialization in production, define explicit schemas.
9) Production Patterns for Map
9.1 Cache (memoization)
const cache = new Map<string, number>();
function expensive(key: string): number {
const hit = cache.get(key);
if (hit !== undefined) return hit;
const value = key.length * 42; // placeholder
cache.set(key, value);
return value;
}
9.2 Frequency table (token → count)
const counts = new Map<string, number>();
for (const token of ["a", "b", "a"]) {
counts.set(token, (counts.get(token) ?? 0) + 1);
}
9.3 Adjacency list (graph)
type Node = string;
const edges = new Map<Node, Set<Node>>();
function link(a: Node, b: Node) {
(edges.get(a) ?? edges.set(a, new Set()).get(a)!).add(b);
}
9.4 Avoid the “wrongMap” pitfall
This is wrong:
const wrongMap = new Map();
wrongMap["bla"] = "blaa"; // ❌ not stored in the Map data structure
console.log(wrongMap.has("bla")); // false
Always use .set() / .get().
10) Production Patterns for Set
10.1 De‑dup
const deduped = [...new Set([2, 13, 4, 4, 2, 13])];
10.2 Membership guard (authorization, feature flags, cohorts)
const allowedRoles = new Set(["admin", "support"]);
function canAccess(role: string) {
return allowedRoles.has(role);
}
10.3 Visited set (graphs, crawlers)
const visited = new Set<string>();
function visit(url: string) {
if (visited.has(url)) return false;
visited.add(url);
return true;
}
10.4 Set algebra (modern JS)
Many runtimes now support operations like union, intersection, etc. If available in your target, they enable clean math-like code.
Fallback is easy:
const union = <T>(a: Set<T>, b: Set<T>) => new Set([...a, ...b]);
11) Pitfalls and Anti‑Patterns
11.1 “Map for everything” (overkill)
If your keys are known at compile-time (fixed schema), a plain object or class is clearer.
11.2 “Set for ordered lists”
Sets preserve insertion order, but they are not a drop-in list replacement:
- no random access by index
- semantics are uniqueness/membership, not ordering
11.3 Mutating objects used as Map keys
If you use a mutable object as a key, remember: identity stays the same, so lookups still work—but your conceptual model may drift.
Prefer immutable keys (IDs, symbols) for long-lived maps.
11.4 WeakMap / WeakSet confusion
Use WeakMap / WeakSet when:
- keys must be objects
- you want garbage collection to reclaim entries automatically
They are not iterable and have different semantics.
12) TypeScript Tips
Strongly type collections
const usersById = new Map<string, { id: string; name: string }>();
const activeUserIds = new Set<string>();
Prefer narrow unions for keys
type CacheKey = `user:${string}` | `org:${string}`;
const cache = new Map<CacheKey, unknown>();
Convert Map to Record safely (only if keys are strings)
function mapToRecord<V>(m: Map<string, V>): Record<string, V> {
return Object.fromEntries(m.entries());
}
13) Practical Cheat Sheet
Choose Map when…
- keys are not guaranteed to be strings
- you need stable iteration in insertion order
- you need
sizecheaply - you accept that JSON needs explicit encoding
- you want safety against prototype collisions
Choose Set when…
- you need uniqueness
- you need fast membership tests
- you need set operations (union/intersection)
- you want to de-dup without extra bookkeeping
Choose plain Object when…
- you have a fixed schema (like DTOs)
- you need native JSON
- keys are internal and controlled (not user-provided)
- you want the simplest shape for APIs
Conclusion
Map and Set are not “nice-to-have ES6 features.”
They encode different mathematical models with different runtime guarantees:
- Map = relationship (key → value)
- Set = membership (unique values)
When your code reflects the correct model, performance, correctness, and maintainability follow naturally.
If you want a one-liner:
Use
Mapfor association, useSetfor uniqueness.
✍️ Cristian Sifuentes
Full‑stack engineer • JavaScript/TypeScript • Systems mindset

Top comments (0)