Academy
ProgrammingJS IntermediateSymbols, WeakMap & WeakRef

Symbols, WeakMap & WeakRef

The Problem With String Keys

Every property key on an object is either a string or a Symbol. You have only used strings so far. The problem with strings: they collide.

If two independent pieces of code both add a property called "id" to the same object, one silently overwrites the other. In small programs this is manageable. In libraries that touch objects they don't own, it is a serious bug waiting to happen.

String keys are a shared namespace. Anyone who knows the name can read or overwrite your property. Symbols solve this entirely.

Symbols — Guaranteed Unique Keys

Symbol() creates a value that is always unique — no two Symbols are ever equal, even if they have the same description:

The Symbol is the only key that can access that slot. Nobody else can accidentally read or overwrite it — because nobody else has that exact Symbol object.

Symbols Are Invisible to Normal Enumeration

Symbol keys are skipped by for...in, Object.keys(), and JSON.stringify() — they are genuinely hidden from casual inspection:

This invisibility is intentional. Symbol-keyed properties are metadata — they do not appear in logs, serialisation, or normal iteration unless you go looking for them specifically.

Well-Known Symbols — Hooking Into Language Behaviour

You already met Symbol.iterator in the previous lesson. JavaScript has a set of well-known Symbols that let you customise how built-in operations treat your objects:

Well-known Symbols are how you make custom classes behave naturally with operators, console.log, template literals, and the rest of the language — without monkey-patching anything.

Symbol.for — Shared Global Symbols

Symbol() always creates a new unique Symbol. Symbol.for(key) looks up a global registry — the same key always returns the same Symbol, even across different files:

WeakMap — Private Data Without Memory Leaks

A regular Map holds strong references to its keys — if you store an object as a key, the Map prevents that object from being garbage collected even if nothing else references it. WeakMap holds weak references: if the key object is no longer referenced anywhere else, it can be garbage collected and the WeakMap entry disappears automatically.

WeakMap keys must be objects (not primitives). You cannot iterate a WeakMap, get its size, or list its keys — by design. These restrictions are what allow the garbage collector to do its job.

WeakSet — Tracking Objects Without Owning Them

WeakSet is like Set but holds weak references. Useful for tracking which objects have been "seen" or "processed" without preventing their cleanup:

WeakRef — Holding Without Owning

WeakRef wraps an object in a weak reference — you can hold onto it without preventing garbage collection. Call .deref() to get the object back; if it has been collected, you get undefined:

Putting It Together — A Private Event System

Here is a realistic pattern that combines Symbols and WeakMap: a private event emitter where the internal state is completely hidden from outside code:

What You Have Learned

Symbols, WeakMap, WeakSet, and WeakRef are precision tools — you will not reach for them daily, but when you need them there is no substitute.

The key ideas:

  • Symbol() creates a guaranteed-unique key — two Symbols with the same description are never equal
  • Symbol keys are invisible to for...in, Object.keys, and JSON.stringify — genuine metadata
  • Well-known Symbols (Symbol.iterator, Symbol.toPrimitive, Symbol.toStringTag) let you hook into built-in language behaviour
  • Symbol.for(key) uses a global registry — same key, same Symbol, across files
  • WeakMap stores private data keyed by object — entries disappear automatically when the key object is garbage collected
  • WeakSet tracks objects without owning them — no iteration, no size, no leaks
  • WeakRef holds an object without preventing collection — always guard .deref() against undefined

In the next lesson: Proxies & Reflect — intercept and redefine fundamental operations on any object. Property access, assignment, function calls, instanceof — every operation JavaScript performs on an object can be trapped and customised. The foundation of validation libraries, reactive frameworks, and ORMs.