Map, Set, WeakMap & WeakSet

10 questions found

How does a Map differ from a plain object for storing key-value pairs?

Beginner
A Map allows keys of ANY type (objects, functions, even NaN), preserves insertion order reliably, has a .size property, and doesn't come with inherited prototype properties that could collide with your keys — plain objects only reliably support string/Symbol keys.
const map = new Map();
map.set('name', 'Sam');
map.set(42, 'answer');
console.log(map.get(42)); // 'answer'
console.log(map.size);   // 2
Real-world example Using an object instance as a key to associate metadata with it, which a plain object can't do.

Common follow-ups: Why might using an object as a plain-object key silently fail (via string coercion)?

Objects Property Descriptors & Immutability

How does a Set differ from an array?

Beginner
A Set only stores unique values — adding a duplicate is a no-op — and lookups with .has() are much faster (O(1) on average) than an array's .includes() (O(n)).
const set = new Set([1, 2, 2, 3]);
console.log(set.size);      // 3
console.log(set.has(2));    // true
Real-world example Tracking which user IDs have already been processed to avoid duplicate work.

Common follow-ups: How do you convert a Set back into an array?

Arrays & Array Methods

How do you iterate over a Map's entries?

Beginner
A Map is directly iterable with for...of, yielding [key, value] pairs by default, matching the order items were inserted; you can also use .keys(), .values(), or .entries() explicitly.
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
  console.log(key, value); // 'a' 1, then 'b' 2
}
Real-world example Iterating over a Map of cached API responses keyed by request URL.

Common follow-ups: Does destructuring work directly in the for...of loop header like this?

Destructuring Spread & Rest

What is a WeakMap and why can't you iterate over its keys?

Intermediate
A WeakMap holds its keys weakly, meaning entries don't prevent the garbage collector from reclaiming a key object once there are no other references to it — this makes iteration (or even .size) impossible, since the set of live keys can shrink at any unpredictable moment via GC.
const cache = new WeakMap();
let obj = { id: 1 };
cache.set(obj, 'metadata');
obj = null; // the entry can now be garbage collected automatically
Real-world example Attaching private metadata to DOM elements without preventing them from being garbage collected when removed from the page.

Common follow-ups: What restrictions does WeakMap place on what can be used as a key?

Memory Management & Garbage Collection

When would you choose a WeakSet over a Set?

Intermediate
WeakSet holds object references weakly (like WeakMap), so it's useful for tracking membership (e.g. 'has this object already been processed?') without creating a memory leak by keeping those objects alive forever once they're no longer used elsewhere.
const visited = new WeakSet();
function process(obj) {
  if (visited.has(obj)) return;
  visited.add(obj);
  // ... do work
}
Real-world example Marking objects as 'already visited' during a recursive traversal without leaking memory for large object graphs.

Common follow-ups: Why can't a WeakSet store primitive values like numbers or strings?

Memory Management & Garbage Collection

How do you convert between a Map and a plain object?

Intermediate
Object.fromEntries(map) converts a Map's entries into a plain object; conversely, new Map(Object.entries(obj)) builds a Map from a plain object's own enumerable properties.
const map = new Map([['a', 1], ['b', 2]]);
const obj = Object.fromEntries(map); // { a: 1, b: 2 }
const backToMap = new Map(Object.entries(obj));
Real-world example Converting a Map used internally for fast lookups into a plain object before sending it as JSON.

Common follow-ups: Does JSON.stringify() work directly on a Map, or does it need this conversion first?

JSON & Data Serialization

How would you use a Map to implement an LRU (Least Recently Used) cache?

Advanced
A Map's guaranteed insertion order lets you track recency: on each access, delete and re-insert the key to move it to the 'most recent' end; when the cache exceeds its size limit, delete the first (oldest) key, obtained via map.keys().next().value.
class LRUCache {
  #cache = new Map();
  #limit;
  constructor(limit) { this.#limit = limit; }
  get(key) {
    if (!this.#cache.has(key)) return undefined;
    const value = this.#cache.get(key);
    this.#cache.delete(key);
    this.#cache.set(key, value); // move to most-recent
    return value;
  }
  set(key, value) {
    if (this.#cache.has(key)) this.#cache.delete(key);
    else if (this.#cache.size >= this.#limit) {
      this.#cache.delete(this.#cache.keys().next().value); // evict oldest
    }
    this.#cache.set(key, value);
  }
}
Real-world example Caching a limited number of recently fetched API responses in a browser app to reduce redundant network calls.

Common follow-ups: How would you add time-based expiration on top of this LRU implementation?

Design Patterns in JavaScript

How do WeakRef and FinalizationRegistry extend the weak-reference capabilities beyond WeakMap/WeakSet?

Advanced
WeakRef lets you hold a weak reference to ANY object (not just as a map key) and later try to access it via .deref(), which returns undefined once collected. FinalizationRegistry lets you register a callback to run (at some unpredictable future point) after an object is actually garbage collected — useful for cleanup, though never guaranteed to run promptly or at all.
const registry = new FinalizationRegistry((heldValue) => {
  console.log('cleaned up:', heldValue);
});
let obj = { data: 'large' };
registry.register(obj, 'obj-label');
obj = null; // callback MAY run later, after GC
Real-world example Releasing an external resource (like a native handle) tied to a JS object once that object is no longer reachable.

Common follow-ups: Why does the spec explicitly discourage relying on FinalizationRegistry for critical program logic?

Memory Management & Garbage Collection

Why can't you use WeakMap for caching results keyed by primitive values like strings or numbers?

Advanced
WeakMap keys must be objects (or, more recently, Symbols) specifically because weak references only make sense for garbage-collectable heap objects — primitives are immutable values, not references, so there's no 'object' for the GC to reclaim and the weak-reference mechanism doesn't apply.
const cache = new WeakMap();
// cache.set('key', 'value'); // TypeError: Invalid value used as weak map key
Real-world example Realizing a memoization cache needs a regular Map (not WeakMap) when the cache keys are strings or numbers rather than objects.

Common follow-ups: What is the recently-added registered Symbol behavior that allows Symbols as WeakMap keys?

Numbers Math & BigInt

How would you group an array of objects by a property using Map, and how does the newer Object.groupBy() compare?

Advanced
Iterate the array, computing a key for each item, and push it into a Map bucket for that key (creating the bucket on first use) — this preserves object references as keys if needed. The newer Object.groupBy() (and Map.groupBy()) built-ins do this in one call, returning a null-prototype object or Map respectively.
const byRole = new Map();
for (const user of users) {
  const key = user.role;
  if (!byRole.has(key)) byRole.set(key, []);
  byRole.get(key).push(user);
}

// Newer built-in:
const grouped = Map.groupBy(users, u => u.role);
Real-world example Grouping a flat list of orders by customer ID before rendering them as separate sections in a UI.

Common follow-ups: What browser/runtime support considerations apply to Object.groupBy() and Map.groupBy()?

Arrays & Array Methods