Map and Set

JavaScript · Practical · 10 min

They are the right data structures for caches, lookups, de-duplication and membership tests.

A Map stores key–value pairs where keys can be any value — objects, functions, numbers — not just strings:

const visits = new Map();
visits.set(user, 3);
visits.get(user);   // 3

Its API: set, get, has, delete, clear and the size property. Maps remember insertion order and are directly iterable: for (const [key, value] of map), plus keys(), values() and entries().

Prefer a Map over a plain object when keys are dynamic or user-provided, when you add and remove a lot, or when keys aren't strings. Objects have inherited properties ("toString" in {} is true) and string-only keys; Maps have neither problem. Convert with new Map(Object.entries(obj)) and Object.fromEntries(map). Map.groupBy(items, fn) (ES2024) builds a Map of groups.

A Set holds unique values. add, has, delete and size work as you'd expect; has is fast compared to array.includes on large collections. The quickest de-duplication is [...new Set(array)].

Both use SameValueZero equality: like ===, except NaN equals NaN. Objects are compared by reference, so two different { id: 1 } objects are two different keys.

ES2025 adds set algebra: a.union(b), a.intersection(b), a.difference(b), a.symmetricDifference(b), and isSubsetOf, isSupersetOf, isDisjointFrom. Each returns a new Set or a boolean.

WeakMap and WeakSet hold their object keys weakly, so entries vanish when the key object is garbage-collected — ideal for attaching metadata to objects you don't own.

Example

const ada = { name: "Ada" };
const roles = new Map([[ada, "admin"]]);
roles.set("guest-42", "viewer");          // keys of any type
console.log(roles.get(ada), roles.size);  // admin 2

for (const [who, role] of roles) {
  console.log(typeof who, role);          // object admin / string viewer
}

const tags = ["js", "web", "js", "node", "web"];
const unique = [...new Set(tags)];
console.log(unique);                      // [ 'js', 'web', 'node' ]

const frontend = new Set(["js", "css", "html"]);
const backend = new Set(["js", "sql"]);
console.log(frontend.intersection(backend)); // Set(1) { 'js' }
console.log(frontend.union(backend).size);   // 4
console.log(new Set([NaN, NaN]).size);       // 1 (SameValueZero)

const counts = Object.fromEntries(new Map([["a", 1], ["b", 2]]));
console.log(counts);                      // { a: 1, b: 2 }

Remember

  • Map: any key type, insertion order, size, directly iterable
  • Set: unique values, fast has; [...new Set(arr)] de-duplicates
  • ES2025 sets have union, intersection, difference and friends

Watch out for

  • Using objects as Map keys and expecting a different-but-equal object to find the entry.

Check yourself

  1. What is new Set([1, "1", 1, NaN, NaN]).size?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map