Set, Map, WeakSet, WeakMap
Purpose-Built Collections
LinkedIn Hook
const unique = [...new Set(array)];One line. Order-preserving dedup. No
indexOf, no helper libraries, nohasOwnPropertydance.That is the cleanest example of why ES6 gave us purpose-built collections. Before 2015, developers abused plain objects as makeshift maps (keys got stringified, prototype keys leaked in,
sizewas O(n)) and arrays as makeshift sets (O(n)has, no uniqueness guarantee).
Setgives you unique values with O(1) lookup.Mapgives you any-type keys and insertion-ordered iteration.WeakMapandWeakSethold their keys weakly — so you can attach private data to DOM nodes or class instances without creating a memory leak.In this lesson I walk through all four, plus
WeakRef, with the exact patterns interviewers ask about: Set operations, Map-vs-Object tradeoffs, and WeakMap for truly private state.If you are still reaching for
{}and[]as collection types — this lesson is an upgrade.Read the full lesson -> [link]
#JavaScript #InterviewPrep #ES6 #DataStructures #Map #Set #WeakMap #CodingInterview
What You'll Learn
- Set operations as one-liners — union, intersection, difference, symmetric difference
- Map vs Object tradeoffs (key types, iteration order,
.size, serialization) - Weak collections — WeakMap, WeakSet, and WeakRef — for GC-friendly private data and caches
Why ES6 Collections Exist
Before ES6, developers used plain objects as makeshift maps and arrays as makeshift sets. It worked, but had quirks — objects coerce keys to strings, arrays have no efficient has() check. ES6 gave us purpose-built collections.
Set — Unique Values Collection
const set = new Set([1, 2, 3, 3, 2, 1]);
console.log(set); // Set {1, 2, 3}
console.log(set.size); // 3
// Core methods
set.add(4); // Set {1, 2, 3, 4}
set.has(3); // true
set.delete(2); // true, Set {1, 3, 4}
set.clear(); // Set {}
// Iteration
for (const item of set) { /* ... */ }
set.forEach(item => { /* ... */ });
// Quick dedup
const unique = [...new Set([1, 1, 2, 2, 3])]; // [1, 2, 3]
Set Operations — One-Liners
const a = new Set([1, 2, 3, 4]);
const b = new Set([3, 4, 5, 6]);
// Union
const union = new Set([...a, ...b]);
// Set {1, 2, 3, 4, 5, 6}
// Intersection
const intersection = new Set([...a].filter(x => b.has(x)));
// Set {3, 4}
// Difference (a - b)
const difference = new Set([...a].filter(x => !b.has(x)));
// Set {1, 2}
// Symmetric difference (in either, but not both)
const symDiff = new Set([...a].filter(x => !b.has(x)).concat([...b].filter(x => !a.has(x))));
// Set {1, 2, 5, 6}
Map — Key-Value with Any Key Type
const map = new Map();
// Any value can be a key — objects, functions, primitives
const objKey = { id: 1 };
const fnKey = () => {};
map.set("name", "Rakibul");
map.set(42, "the answer");
map.set(objKey, "object value");
map.set(fnKey, "function value");
map.get("name"); // "Rakibul"
map.get(objKey); // "object value"
map.has(42); // true
map.size; // 4
map.delete(42); // true
// Initialize from entries
const map2 = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
// Iteration (insertion order guaranteed)
for (const [key, value] of map2) {
console.log(key, value);
}
Map vs Object — Comparison Table
| Feature | Map | Object |
|---|---|---|
| Key types | Any value (objects, functions, primitives) | Strings and Symbols only |
| Key order | Insertion order guaranteed | Mostly insertion order (integers first) |
| Size | map.size (O(1)) | Object.keys(obj).length (O(n)) |
| Iteration | Directly iterable (for...of) | Need Object.keys/values/entries |
| Performance | Better for frequent add/delete | Better for static structures |
| Default keys | None | Has prototype keys (toString, etc.) |
| Serialization | No native JSON support | JSON.stringify works directly |
| Memory | More memory-efficient for large datasets | Less overhead for small datasets |
WeakMap — Weak References for Keys
Keys must be objects (not primitives) and are held weakly — if no other reference exists, the entry is garbage collected.
// WeakMap for truly private class data
const _private = new WeakMap();
class User {
constructor(name, password) {
this.name = name;
_private.set(this, { password, loginAttempts: 0 });
}
checkPassword(input) {
const data = _private.get(this);
data.loginAttempts++;
return input === data.password;
}
getLoginAttempts() {
return _private.get(this).loginAttempts;
}
}
const user = new User("Rakibul", "secret123");
user.checkPassword("wrong"); // false
user.checkPassword("secret123"); // true
user.getLoginAttempts(); // 2
// No way to access _private data from outside
console.log(user.password); // undefined
// When 'user' is garbage collected, the WeakMap entry is too
WeakMap for DOM Metadata
const elementData = new WeakMap();
function trackElement(element) {
elementData.set(element, {
clickCount: 0,
lastClicked: null
});
}
function handleClick(element) {
const data = elementData.get(element);
data.clickCount++;
data.lastClicked = Date.now();
}
// When the DOM element is removed and garbage collected,
// the WeakMap entry is automatically cleaned up — no memory leak
WeakSet
// Track which objects have been processed
const processed = new WeakSet();
function processOnce(obj) {
if (processed.has(obj)) {
console.log("Already processed");
return;
}
processed.add(obj);
// ... process the object
}
const data = { id: 1 };
processOnce(data); // processes
processOnce(data); // "Already processed"
WeakRef
// WeakRef holds a weak reference to an object
let target = { data: "important" };
const ref = new WeakRef(target);
// Access the target (may be garbage collected)
console.log(ref.deref()); // { data: "important" }
target = null; // Remove strong reference
// At some point, ref.deref() will return undefined
// after garbage collection runs
// Use case: caches that don't prevent GC
class Cache {
constructor() {
this.cache = new Map();
}
set(key, value) {
this.cache.set(key, new WeakRef(value));
}
get(key) {
const ref = this.cache.get(key);
if (ref) {
const value = ref.deref();
if (value !== undefined) return value;
this.cache.delete(key); // Clean up dead reference
}
return undefined;
}
}
Common Mistakes
- Using a Set of objects and expecting value equality — Sets and Maps use reference equality for objects. Two objects with identical contents are two different keys.
- Trying to iterate a WeakMap or WeakSet or read its
size— neither is iterable and neither exposes size. That is a deliberate design choice so GC can collect entries without observable side effects. - Assuming
JSON.stringify(map)serializes your Map — it returns"{}". You need to convert to an array of entries first:JSON.stringify([...map])orJSON.stringify(Object.fromEntries(map)).
Interview Questions
Q: When should you use Map over a plain object?
Use Map when: keys are not strings/symbols, you need insertion-order iteration, you frequently add/delete entries, you need
.size, or you want to avoid prototype pollution. Use objects for: static configuration, JSON serialization, and simple string-keyed data.
Q: How do Set operations (union, intersection, difference) work?
Union:
new Set([...a, ...b]). Intersection:new Set([...a].filter(x => b.has(x))). Difference:new Set([...a].filter(x => !b.has(x))). These leverage spread to convert Sets to arrays for filtering, then create new Sets.
Q: What is WeakMap and why would you use it?
WeakMap holds key-value pairs where keys must be objects and are held weakly (eligible for garbage collection if no other references exist). It's not iterable and has no
.size. Use cases: storing private class data, DOM element metadata, and caches that shouldn't prevent GC.
Q: What is the difference between WeakRef and WeakMap?
WeakRef holds a single weak reference to any object, accessed via
.deref(). WeakMap is a collection of key-value pairs with weakly-held object keys. WeakRef is lower-level and used for caches and observing GC. WeakMap is used for associating data with objects without preventing their collection.
Q: Why is a Set a great way to dedupe an array?
new Set(arr)preserves insertion order and guarantees uniqueness via reference/value equality for primitives;[...new Set(arr)]gives you an array back in one line, with roughly O(n) complexity — far better than nestedindexOffilters.
Quick Reference — Cheat Sheet
COLLECTIONS — QUICK MAP
Key-Value Values-Only
Strong: Map Set
Weak: WeakMap WeakSet
Map:
new Map([[k,v],...]) .get .set .has .delete .size
any key type | insertion-ordered | iterable
Set:
new Set([v,...]) .add .has .delete .size
unique values | insertion-ordered | iterable
Dedup one-liner: [...new Set(arr)]
WeakMap:
object keys only | held weakly | NOT iterable | no size
use: private data, DOM metadata, GC-friendly caches
WeakSet:
object values only | held weakly | NOT iterable | no size
use: "has this object been processed?" flags
WeakRef:
ref = new WeakRef(obj) ref.deref() -> obj or undefined
use: low-level caches that must not prevent GC
Set operations (one-liners):
union -> new Set([...a, ...b])
intersect -> new Set([...a].filter(x => b.has(x)))
difference -> new Set([...a].filter(x => !b.has(x)))
symmetric -> union of (a - b) and (b - a)
Previous: Iterators and Iterables -> The Protocol Behind for...of Next: Proxy and Reflect -> Metaprogramming the Right Way
This is Lesson 9.7 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.