Iterables, Maps, Sets & Cloning
In one line
Anything with a [Symbol.iterator] method works with for...of, spread, and destructuring — including objects you write yourself.
What it is
The iterable protocol is one method: [Symbol.iterator]() returning an iterator, which is any object with a next() that returns { value, done }. Arrays, strings, Map, Set, NodeList, and arguments all implement it. Plain objects do not, which is why for...of over an object throws and you need Object.entries first.
Everything that "just works" on arrays is this protocol: for...of, array spread, destructuring, Array.from, Promise.all's argument. Implement [Symbol.iterator] on your own class — most easily with a generator method — and all of it works on your type too.
Map vs object is a real decision, not a style preference. Map accepts any value as a key, including objects and functions; object keys are coerced to strings, so obj[1] and obj["1"] collide. Map preserves insertion order for all key types and exposes .size directly. It has no prototype chain, so there's no "constructor" or "__proto__" key hazard and no need for hasOwnProperty guards. Use Map when keys are dynamic, non-string, or when you're inserting and deleting frequently; use an object for fixed, known-shape records — and note that objects serialise to JSON while Maps do not.
Set gives O(1) membership and deduplication: [...new Set(arr)] is the idiomatic unique. It uses SameValueZero, so NaN deduplicates correctly — unlike indexOf.
WeakMap and WeakSet hold keys weakly: an entry disappears when its key is garbage collected. This makes them the correct tool for attaching metadata to objects you don't own — caching a computed value per DOM node, tracking which objects a library has already processed — without preventing those objects from being collected. They aren't iterable and have no .size, precisely because their contents can vanish at any time.
Cloning has three levels worth distinguishing. Spread and Object.assign are shallow — nested objects stay shared. JSON.parse(JSON.stringify(x)) is deep but lossy: it drops undefined, functions, and Symbols, turns Date into a string, and throws on cycles. structuredClone is the built-in correct answer — deep, handles Date, Map, Set, ArrayBuffer, and cyclic references, and fails loudly on functions and DOM nodes rather than silently mangling them.
Why it matters
Choosing Map over an object for a lookup keyed by ID, or a Set for membership checks instead of array.includes inside a loop, is the difference between O(1) and O(n²) — and it's exactly the kind of thing a reviewer flags in a take-home with a large list.
WeakMap shows up whenever you need per-object state without a leak, and being able to name it as the answer is a small but real senior signal. Cloning comes up constantly and structuredClone is still under-known.
Key points
- The iterable protocol is a single
[Symbol.iterator]method, and implementing it — usually as a generator — makes your own type work withfor...of, spread, and destructuring. - Plain objects are not iterable;
for...ofrequiresObject.keys,Object.values, orObject.entriesfirst. Mapkeys can be any value and preserve insertion order, while object keys are coerced to strings — soobj[1]andobj["1"]are the same property.Maphas no prototype chain, avoiding the inherited-key hazards that makehasOwnPropertyguards necessary on objects.- Objects serialise to JSON and Maps do not, which is usually the deciding factor when the value crosses a network or storage boundary.
Setprovides O(1) membership; replacingarray.includesinside a loop with aSetturns O(n²) into O(n).WeakMapholds keys weakly, making it the right way to attach metadata to objects you don't own without preventing their collection.structuredCloneis the correct deep clone — theJSON.parse(JSON.stringify(x))idiom silently dropsundefined, functions, andSymbols and destroysDateobjects.