JavaScript Interview Prep
ES6+ Features

Set, Map, WeakSet, WeakMap

Purpose-Built Collections

LinkedIn Hook

const unique = [...new Set(array)];

One line. Order-preserving dedup. No indexOf, no helper libraries, no hasOwnProperty dance.

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, size was O(n)) and arrays as makeshift sets (O(n) has, no uniqueness guarantee).

Set gives you unique values with O(1) lookup. Map gives you any-type keys and insertion-ordered iteration. WeakMap and WeakSet hold 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


Set, Map, WeakSet, WeakMap thumbnail


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

FeatureMapObject
Key typesAny value (objects, functions, primitives)Strings and Symbols only
Key orderInsertion order guaranteedMostly insertion order (integers first)
Sizemap.size (O(1))Object.keys(obj).length (O(n))
IterationDirectly iterable (for...of)Need Object.keys/values/entries
PerformanceBetter for frequent add/deleteBetter for static structures
Default keysNoneHas prototype keys (toString, etc.)
SerializationNo native JSON supportJSON.stringify works directly
MemoryMore memory-efficient for large datasetsLess 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;
  }
}

Set, Map, WeakSet, WeakMap visual 1


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]) or JSON.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 nested indexOf filters.


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.

On this page