Map, Set, WeakMap, WeakSet

Understand the four ES6 collection types — Map, Set, WeakMap, and WeakSet — how they differ from plain objects and arrays, and when to reach for each one.

Before ES6, JavaScript had only two general-purpose data structures for grouping values: objects (for key-value pairs) and arrays (for ordered lists). ES6 added four dedicated collection types that solve specific problems objects and arrays handle awkwardly.

Map: key-value pairs done right

A Map stores key-value pairs, similar to an object, but with crucial advantages: keys can be ANY value (not just strings/symbols like object keys), the insertion order is always preserved during iteration, and you get a .size property instead of manually counting keys.

Common Map operations are all methods rather than bracket syntax: map.set(key, value), map.get(key), map.has(key), map.delete(key), and map.size. You iterate a Map with for...of because Maps are directly iterable, yielding [key, value] pairs.

Set: unique values

A Set stores a collection of unique values — if you try to add a value that already exists in the Set, it is silently ignored. This makes Sets the natural tool for deduplicating data or tracking membership ("has this ID already been seen?").

Core Set operations: set.add(value), set.has(value), set.delete(value), and set.size. Like Map, a Set is iterable with for...of, and iteration order matches insertion order.

Map and Set vs plain objects and arrays

It's tempting to think Map is "just an object" and Set is "just an array", but they solve real limitations. Objects only allow string or symbol keys — using an object as a key silently coerces it to the string "[object Object]". Maps allow objects, functions, or any value as a real, distinct key.

Checking whether an array contains a value (array.includes(x)) is O(n) — it must scan every element. Checking set.has(x) is close to O(1) on average, because Sets are backed by hash-table-like structures. For large collections where membership checks happen often, a Set is significantly faster.

• Map keys can be any type; object keys are limited to strings and symbols. • Map has a direct .size property; objects require Object.keys(obj).length. • Map preserves insertion order reliably; plain objects mostly do, but with edge cases around integer-like keys. • Set automatically deduplicates; arrays do not.

WeakMap and WeakSet: memory-friendly variants

WeakMap and WeakSet are restricted versions of Map and Set: their keys (WeakMap) or values (WeakSet) MUST be objects, not primitives, and those references are "weak" — meaning they do not prevent the JavaScript garbage collector from reclaiming that object's memory if nothing else references it.

Because of this weak referencing, WeakMap and WeakSet are NOT iterable, have no .size, and cannot be looped over — you can only .get(), .set(), .has(), and .delete() by a specific object reference. This trade-off exists specifically to avoid memory leaks.

A classic use case: attaching private, extra metadata to a DOM element or object without preventing it from being garbage-collected once it's removed from the page. A regular Map holding a DOM node as a key would keep that node in memory forever, even after it's removed from the document; a WeakMap lets it be cleaned up normally.

Quick reference

new Set(array) is the standard one-liner to deduplicate an array: [...new Set(array)]. Map/Set constructors accept an iterable to pre-populate them: new Map([['a',1],['b',2]]). WeakMap/WeakSet throw a TypeError if you try to use a primitive (string, number, etc.) as the key/value.

// Map
const map = new Map();
map.set("a", 1).set("b", 2);
map.get("a");     // 1
map.has("b");     // true
map.size;         // 2
for (const [key, value] of map) { /* ... */ }

// Set
const set = new Set([1, 2, 2, 3]);
set.add(4);
set.has(3);       // true
set.size;         // 4 (duplicates removed)

// WeakMap (keys must be objects, not iterable)
const wm = new WeakMap();
const el = {};
wm.set(el, "metadata");
wm.get(el);       // "metadata"

// WeakSet (values must be objects, not iterable)
const ws = new WeakSet();
ws.add(el);
ws.has(el);       // true

Code Examples

Creating a Set from an array with duplicate values automatically removes the duplicates, and spreading it back with [...uniqueTags] converts it into a plain array again. Expected result: 3 [ 'js', 'html', 'css' ] new Set([...]) is the idiomatic one-line way to deduplicate an array.

A Map cleanly models a username-to-role lookup with guaranteed insertion-order iteration and a built-in size, which is more explicit than doing the equivalent with a plain object. Expected result: maya: admin alex: editor sam: viewer Total users: 3 for (const [username, role] of userRoles) destructures each Map entry directly in the loop.

This uses a WeakMap as a private-storage technique (a pattern common before native #private class fields existed): the actual balance lives in a WeakMap keyed by the instance, so it is inaccessible from outside the class and is automatically cleaned up if the account object is ever discarded. Expected result: 150 undefined privateData.set(this, { balance }) stores state outside the object itself, keyed weakly by the instance.

const uniqueTags = new Set(["js", "html", "css", "js", "html"]);
console.log(uniqueTags.size);
console.log([...uniqueTags]);
const userRoles = new Map();
userRoles.set("maya", "admin");
userRoles.set("alex", "editor");
userRoles.set("sam", "viewer");

for (const [username, role] of userRoles) {
  console.log(`${username}: ${role}`);
}
console.log("Total users:", userRoles.size);
const privateData = new WeakMap();

class BankAccount {
  constructor(owner, balance) {
    privateData.set(this, { balance });
    this.owner = owner;
  }
  deposit(amount) {
    privateData.get(this).balance += amount;
  }
  getBalance() {
    return privateData.get(this).balance;
  }
}

const acct = new BankAccount("Jordan", 100);
acct.deposit(50);
console.log(acct.getBalance());
console.log(acct.balance); // undefined — truly private, not stored on the instance

Important Rules

Use Map when you need arbitrary key types, guaranteed order, or frequent additions/removals of key-value pairs.

Use Set when you need to deduplicate values or perform fast membership checks.

Use WeakMap/WeakSet only when the keys/values are objects you want to avoid memory-leaking by holding onto.

WeakMap and WeakSet are never iterable and have no .size — they only support get/set/has/delete (and add for WeakSet).

Where this shows up

Sets are used constantly for deduplicating IDs, tags, or permissions lists. Maps power caches, lookup tables, and any place a plain object's string-only keys are limiting. WeakMaps back private-field polyfills, memoization caches keyed by object identity, and frameworks that attach metadata to DOM nodes without leaking memory.

const wm = new WeakMap(); wm.set("username", data);

const wm = new WeakMap(); wm.set(userObject, data);

WeakMap keys must be objects (or, in modern engines, registered symbols) — passing a primitive like a string throws a TypeError.

for (const item of myWeakSet) { console.log(item); } if (myWeakSet.has(specificObject)) { /*... */ }

WeakSet (and WeakMap) are not iterable by design, since their contents can be garbage-collected at any time — you can only query specific known references, never enumerate all of them.

[Multiple Choice] Which collection type would be most appropriate for deduplicating a large list of user IDs? A. Array B. Set C. WeakMap D. Object

B. Set — Set automatically discards duplicate values and provides fast has() membership checks, making it the natural tool for deduplication.

[True / False] A WeakMap can be looped over with for...of just like a Map. A. True B. False

B. False — False. WeakMap (and WeakSet) are not iterable and have no size property, because their entries can be garbage-collected at any time — you can only access them via a specific known object reference.

Given an array of visitor IP address strings (with duplicates), use a Set to find how many unique visitors there were, and use a Map to count how many times each IP appears.

function analyzeVisitors(ips) { const uniqueCount = new Set(ips).size; const counts = new Map(); for (const ip of ips) { counts.set(ip, (counts.get(ip) ?? 0) + 1); } return { uniqueCount, counts }; } new Set(ips).size gives unique count; loop the array and use map.set(ip, (map.get(ip) ?? 0) + 1) to count occurrences.