DEV Community

Cristian Sifuentes
Cristian Sifuentes

Posted on

JavaScript Maps vs Sets — A Scientific, Production‑Minded Guide (2026)

Map and Set are not “better
JavaScript Maps vs Sets — A Scientific, Production‑Minded Guide (2026)

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

Use this rule when you don’t want to think too much:

  • If you need lookup by keyMap
  • If you need unique membershipSet
  • 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 ===, but NaN equals NaN
  • Also treats 0 and -0 as 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"
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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 than array.includes(x) for large collections.
  • Map#get(k) is typically faster and cleaner than object[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
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

7.4 Size and iteration ergonomics

  • Map#size is O(1)
  • Object.keys(o).length allocates 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));
Enter fullscreen mode Exit fullscreen mode

Serialize Set

const s = new Set([1, 2, 3]);
const json = JSON.stringify([...s]);
const restored = new Set(JSON.parse(json));
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Always use .set() / .get().


10) Production Patterns for Set

10.1 De‑dup

const deduped = [...new Set([2, 13, 4, 4, 2, 13])];
Enter fullscreen mode Exit fullscreen mode

10.2 Membership guard (authorization, feature flags, cohorts)

const allowedRoles = new Set(["admin", "support"]);

function canAccess(role: string) {
  return allowedRoles.has(role);
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

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>();
Enter fullscreen mode Exit fullscreen mode

Prefer narrow unions for keys

type CacheKey = `user:${string}` | `org:${string}`;
const cache = new Map<CacheKey, unknown>();
Enter fullscreen mode Exit fullscreen mode

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());
}
Enter fullscreen mode Exit fullscreen mode

13) Practical Cheat Sheet

Choose Map when…

  • keys are not guaranteed to be strings
  • you need stable iteration in insertion order
  • you need size cheaply
  • 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 Map for association, use Set for uniqueness.

✍️ Cristian Sifuentes

Full‑stack engineer • JavaScript/TypeScript • Systems mindset

Top comments (0)