Skip to content
elephantoo

Map, Set, WeakMap & WeakSet

Lesson 21 of 34 14 min read

Keyed collections, unique values, the new Set methods and when weak references help.


Arrays and plain objects cover most needs, but JavaScript has four more specialised collections: Map (key–value pairs with any kind of key), Set (unique values), and their "weak" cousins WeakMap and WeakSet. Picking the right one makes code shorter, faster and less buggy.

Map: key–value pairs, any key#

JavaScript
const stock = new Map();

stock.set("apples", 12);
stock.set("bananas", 0).set("cherries", 40); // set() returns the map, so it chains

console.log(stock.get("apples"));   // 12
console.log(stock.get("mangoes"));  // undefined
console.log(stock.has("bananas"));  // true
console.log(stock.size);            // 3

stock.delete("bananas");
console.log(stock.size);            // 2

Create a Map from pairs, or from an object with Object.entries:

JavaScript
const prices = new Map([
  ["tea", 20],
  ["coffee", 45],
]);
const fromObject = new Map(Object.entries({ a: 1, b: 2 }));
console.log(prices.get("coffee"), fromObject.get("b")); // 45 2

Any value can be a key

This is Map's superpower. Object keys are always converted to strings; Map keys keep their type and identity:

JavaScript
const obj = {};
obj[1] = "number one";
obj["1"] = "string one"; // overwrites – both are the key "1"
console.log(Object.keys(obj)); // [ '1' ]

const map = new Map();
map.set(1, "number one");
map.set("1", "string one");
console.log(map.size); // 2 – different keys

const ada = { name: "Ada" };
const alan = { name: "Alan" };
const visits = new Map([[ada, 3], [alan, 1]]);
console.log(visits.get(ada)); // 3 – the object itself is the key

Iterating a Map

Maps remember insertion order and are directly iterable:

JavaScript
const scores = new Map([["ada", 91], ["grace", 87], ["alan", 78]]);

for (const [name, score] of scores) {
  console.log(`${name}: ${score}`);
}

console.log([...scores.keys()]);   // [ 'ada', 'grace', 'alan' ]
console.log([...scores.values()]); // [ 91, 87, 78 ]
scores.forEach((score, name) => console.log(name.toUpperCase(), score >= 80));

// Map → object (e.g. to send as JSON)
console.log(Object.fromEntries(scores)); // { ada: 91, grace: 87, alan: 78 }
Output
ada: 91
grace: 87
alan: 78
[ 'ada', 'grace', 'alan' ]
[ 91, 87, 78 ]
ADA true
GRACE true
ALAN false
{ ada: 91, grace: 87, alan: 78 }

Example: counting with a Map

JavaScript
function wordFrequency(text) {
  const counts = new Map();
  for (const word of text.toLowerCase().match(/[a-z']+/g) ?? []) {
    counts.set(word, (counts.get(word) ?? 0) + 1);
  }
  return counts;
}

const freq = wordFrequency("The cat sat. The cat ran! The end.");
const top = [...freq].toSorted((a, b) => b[1] - a[1]).slice(0, 2);
console.log(top); // [ [ 'the', 3 ], [ 'cat', 2 ] ]

Grouping into a Map

Map.groupBy (ES2024) is like Object.groupBy but produces a Map, so the group keys can be anything:

JavaScript
const lessons = [
  { title: "Loops", minutes: 13 },
  { title: "Promises", minutes: 18 },
  { title: "Conditionals", minutes: 12 },
];
const bySize = Map.groupBy(lessons, (l) => (l.minutes > 15 ? "long" : "short"));
console.log(bySize.get("short").map((l) => l.title)); // [ 'Loops', 'Conditionals' ]

Map or object?

Use a Map when…Use an object when…
Keys are unknown until runtime (user ids, words)The shape is fixed ({ name, email })
Keys aren't strings (objects, numbers)You'll convert to/from JSON often
You add/remove entries frequentlyYou want dot access: user.name
You need size or reliable orderingIt's a record describing one thing

Set: unique values#

JavaScript
const tags = new Set(["js", "css", "js", "html", "css"]);
console.log(tags);       // Set(3) { 'js', 'css', 'html' }
console.log(tags.size);  // 3

tags.add("react");
tags.add("js");          // already there – ignored
console.log(tags.has("react")); // true
tags.delete("css");
console.log([...tags]);  // [ 'js', 'html', 'react' ]

Removing duplicates

The most common use of a Set:

JavaScript
const visits = ["home", "about", "home", "blog", "about"];
const uniquePages = [...new Set(visits)];
console.log(uniquePages); // [ 'home', 'about', 'blog' ]

Fast membership checks

set.has(x) is near-instant even with millions of items, while array.includes(x) checks every element:

JavaScript
const banned = new Set(["spam@example.com", "bot@example.com"]);
const signups = ["ada@example.com", "bot@example.com", "alan@example.com"];
const allowed = signups.filter((email) => !banned.has(email));
console.log(allowed); // [ 'ada@example.com', 'alan@example.com' ]

Set operations (ES2025)

Modern engines (Node 22+, current browsers) have built-in set maths:

JavaScript
const frontEnd = new Set(["html", "css", "js"]);
const backEnd = new Set(["js", "sql", "node"]);

console.log(frontEnd.union(backEnd));        // Set(5) { 'html', 'css', 'js', 'sql', 'node' }
console.log(frontEnd.intersection(backEnd)); // Set(1) { 'js' }
console.log(frontEnd.difference(backEnd));   // Set(2) { 'html', 'css' }
console.log(frontEnd.symmetricDifference(backEnd)); // Set(4) { 'html', 'css', 'sql', 'node' }
console.log(new Set(["js"]).isSubsetOf(frontEnd));  // true
console.log(frontEnd.isDisjointFrom(new Set(["go"]))); // true

In older environments, the equivalent is new Set([...a].filter((x) => b.has(x))) for intersection, and so on.

Sets compare values with === semantics (except that NaN equals NaN). Two different objects with the same contents are two entries: new Set([{}, {}]).size is 2.

WeakMap and WeakSet#

A normal Map keeps its keys alive: as long as the Map exists, its key objects can't be garbage-collected. That's a memory leak if you use objects (like DOM elements) as keys and forget to delete them.

WeakMap holds its keys weakly: if nothing else references a key object, the entry silently disappears.

JavaScript
const metadata = new WeakMap();

let user = { name: "Ada" };
metadata.set(user, { lastSeen: Date.now() });
console.log(metadata.has(user)); // true

user = null; // no other references – the entry can now be garbage-collected

Restrictions (which follow from "entries can vanish at any time"):

  • Keys must be objects (or non-registered symbols), not strings or numbers.
  • No size, no iteration, no clear() — only get, set, has, delete.

Use case: private data or caches for objects

JavaScript
const cache = new WeakMap();

function expensiveSummary(report) {
  if (cache.has(report)) return cache.get(report);
  console.log("computing…");
  const summary = { total: report.rows.reduce((s, r) => s + r.amount, 0) };
  cache.set(report, summary);
  return summary;
}

const report = { rows: [{ amount: 100 }, { amount: 250 }] };
console.log(expensiveSummary(report)); // computing… then { total: 350 }
console.log(expensiveSummary(report)); // { total: 350 } – from the cache

When report is no longer used anywhere, its cached summary is freed too.

Use case: data attached to DOM elements

JavaScript
const tooltipState = new WeakMap();

document.querySelectorAll("[data-tooltip]").forEach((el) => {
  tooltipState.set(el, { shown: false, timer: null });
});
// If an element is removed from the page and dropped, its state goes with it.

WeakSet

A WeakSet is a set of objects held weakly — useful for "have I seen/processed this object?":

JavaScript
const processed = new WeakSet();

function processOnce(order) {
  if (processed.has(order)) return "already processed";
  processed.add(order);
  return `processing order ${order.id}`;
}

const order = { id: 42 };
console.log(processOnce(order)); // processing order 42
console.log(processOnce(order)); // already processed

Quick reference#

MapSetWeakMapWeakSet
Storeskey → valueunique valuesobject → valueunique objects
Key typesanythinganythingobjectsobjects
Iterable / size✅✅❌❌
Keeps keys aliveyesyesnono

Common mistakes#

  • Using map[key] = value on a Map — that sets an ordinary property, not an entry. Use map.set(key, value).
  • Expecting JSON.stringify(map) to work — it gives {}. Convert with Object.fromEntries(map) first.
  • Using objects as Set members and expecting content-based de-duplication.
  • Reaching for WeakMap when you need to list or count entries — use a Map.

What's next#

Next: regular expressions — a mini-language for searching, validating and transforming text.

Check your understanding

Quick quiz

0/3 answered
  1. 1.What is new Set([1, 2, 2, 3, 3, 3]).size?

  2. 2.Which is a real advantage of Map over a plain object for a lookup table?

  3. 3.Why would you use a WeakMap to store extra data about DOM elements?

Finished reading?

Mark this lesson complete to track your progress.