# WeakMap and WeakSet

## 1. The Core Difference from Map/Set: Weak References

`WeakMap` and `WeakSet` hold their object keys/values **weakly** — meaning the garbage collector is free to reclaim an object even while it's still a key in a WeakMap, as long as nothing else references it. A regular `Map` holding the same object would keep it alive forever (a memory leak) until explicitly deleted.

```js
let user = { name: "Ana" };
const metadata = new WeakMap();
metadata.set(user, { lastLogin: "2024-01-01" });

user = null; // no other references to the original object remain
// The engine is now free to garbage-collect the { name: "Ana" } object,
// AND its entry in `metadata` is automatically removed along with it —
// no memory leak, no manual cleanup needed.
```

If `metadata` had been a regular `Map`, the object would stay alive in memory indefinitely (still referenced by the Map), even after `user = null`.

![While user exists its WeakMap entry stays; once user = null, both the object and its entry are collected together](diagrams/01-weak-reference-gc.svg)

## 2. Key Restrictions (What Makes Them "Weak")

| | `Map` / `Set` | `WeakMap` / `WeakSet` |
|---|---|---|
| Key/value types | any value | **objects only** (and, in modern engines, registered Symbols) as keys/members — no primitives |
| Iterable? | ✅ yes | ❌ **no** — no `.keys()`, no `for...of`, no `.size` |
| Can check size | ✅ `.size` | ❌ not available |
| Garbage collection | keeps entries alive | entries can be collected when the key is unreachable elsewhere |

```js
const wm = new WeakMap();
wm.set("string key", 1); // TypeError: Invalid value used as weak map key — primitives not allowed
```

The lack of iteration isn't a missing feature — it's deliberate: since entries can disappear at any moment via garbage collection, allowing iteration would make behavior unpredictable and timing-dependent.

## 3. Practical Use Case: Private Data Without `#private` Fields

Before class private fields (`#field`) existed, `WeakMap` was the standard technique for truly private, per-instance data that couldn't be reached from outside a module:

```js
const privateData = new WeakMap();

class BankAccount {
  constructor(balance) {
    privateData.set(this, { balance });
  }
  deposit(amount) {
    const data = privateData.get(this);
    data.balance += amount;
    return data.balance;
  }
  getBalance() {
    return privateData.get(this).balance;
  }
}
```

`privateData` lives in module scope, inaccessible from outside; `WeakMap` ensures that when a `BankAccount` instance is garbage collected, its private data entry disappears with it automatically.

## 4. Practical Use Case: Tagging Objects Without Modifying Them

```js
const processedItems = new WeakSet();

function processOnce(item) {
  if (processedItems.has(item)) return "already processed";
  processedItems.add(item);
  return "processing now";
}
```

This tracks "have I seen this object before" without attaching a visible property to the object itself, and without leaking memory if the object is later discarded elsewhere in the program.

## 5. DOM Node Metadata (Classic Browser Use Case)

```js
const nodeData = new WeakMap();
function attachData(domNode, data) {
  nodeData.set(domNode, data);
}
// If the DOM node is later removed from the page and has no other
// references, both the node AND its associated data are garbage
// collected together — no manual cleanup required.
```

## 6. Under the Hood: Ephemerons — Why This Actually Requires Special GC Support

`WeakMap`/`WeakSet` aren't just "a `Map`/`Set` that happens to allow garbage collection" — supporting them correctly requires the garbage collector itself to understand a specific concept called an **ephemeron**, because naive weak-reference handling produces the wrong answer in a genuinely tricky case.

### The problem a simple "weak pointer" doesn't solve

Imagine the GC just treated a `WeakMap` entry's key as a plain weak pointer (ignorable when deciding what's reachable) but treated the **value** as a normal strong reference. Now consider:

```js
const wm = new WeakMap();
wm.set(keyObj, valueObj); // valueObj might itself be large, or reference OTHER objects
```

If `keyObj` becomes unreachable everywhere else, the entry should disappear — including `valueObj`. But `valueObj` might be reachable through *other* paths too, or might itself hold references back toward things that matter. A correct implementation must decide "is `valueObj` needed?" based on "is `keyObj` reachable?" — a reachability question that depends on *another* object's reachability, not on `valueObj` being referenced directly. That's precisely what an **ephemeron** is: a (key, value) pair where the value's liveness is conditionally dependent on the key's liveness, not independently determined.

### Why this needs a fixed-point algorithm, not a single pass

```
Object A --(strong ref)--> WeakMap entry: key = B, value = C
Object C --(strong ref)--> Object D
Is D reachable?  →  depends on whether C is kept  →  depends on whether B is reachable
                     (through some OTHR path entirely unrelated to this WeakMap)
```

A garbage collector handling ephemerons correctly must run its reachability analysis to a **fixed point**: mark everything reachable through normal strong references first, then check each ephemeron's key — if the key turned out reachable, promote its value (and everything the value references) to reachable too, which might in turn make yet other ephemerons' keys newly relevant, requiring another pass. This repeats until nothing new becomes reachable. This is meaningfully more complex than simple mark-and-sweep, which is precisely why `WeakMap`/`WeakSet` couldn't just be "implemented in userland" efficiently before engines added real ephemeron support — no combination of ordinary weak references achieves this fixed-point semantics correctly on its own.

### Why iteration is fundamentally incompatible, not just omitted for safety

Section 2 notes iteration is deliberately excluded. The ephemeron mechanics explain *why* it's not merely a design preference: an entry's continued existence depends on a garbage-collection decision that can happen at any point the engine chooses, following the fixed-point process above — there is no well-defined moment "between" GC passes where a stable, complete list of current entries could be produced without the act of listing them itself interfering with (or depending on) collection timing.

## 7. Common Pitfalls

1. **Trying to use a primitive as a key** — always throws `TypeError`; only objects (and some Symbols in modern engines) are valid.
2. **Expecting to iterate or check size** — neither exists on `WeakMap`/`WeakSet`, by design; if you need to enumerate entries, you need a regular `Map`/`Set` instead (accepting the memory-retention tradeoff).
3. **Using WeakMap when you actually need to enumerate later** — if your use case requires listing all stored entries, WeakMap is the wrong tool regardless of its memory benefits.
4. **Assuming garbage collection timing is predictable/immediate** — an entry disappears "eventually," not the instant the key becomes unreachable; never write logic that depends on exact GC timing.

## 8. Quick Reference

```
WeakMap: object keys only, no iteration, no .size, entries GC'd with their key
WeakSet: object members only, no iteration, no .size, members GC'd when unreachable
Use for: private instance data, object tagging/metadata, avoiding memory leaks
Don't use for: anything you need to iterate, count, or serialize
```
