10 questions foundA 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
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
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
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
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
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
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
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
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
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