# Symbols

## 1. What a Symbol Is

`Symbol` is a primitive type introduced in ES6 whose only purpose is to be a **guaranteed-unique value**, typically used as an object property key that will never accidentally collide with any other key — including keys from other libraries.

```js
const s1 = Symbol("id");
const s2 = Symbol("id");
console.log(s1 === s2); // false — every Symbol() call creates a distinct value, even with the same description
```

The string passed to `Symbol()` is just a **description** for debugging — it has no effect on uniqueness or equality.

## 2. Symbols as Object Keys

```js
const ID = Symbol("id");

const user = {
  name: "Ana",
  [ID]: 12345, // computed key — stores under the Symbol, not the string "id"
};

console.log(user[ID]);      // 12345
console.log(user["ID"]);    // undefined — a string key "ID" is unrelated to the Symbol ID
console.log(Object.keys(user)); // ["name"] — Symbol keys are excluded by default
```

![String-keyed and symbol-keyed properties live in separate spaces on the same object](diagrams/01-separate-key-spaces.svg)

## 3. Symbols Are Hidden from Normal Enumeration

| Method | Includes Symbol keys? |
|---|---|
| `Object.keys()` / `Object.values()` / `Object.entries()` | ❌ no |
| `for...in` | ❌ no |
| `JSON.stringify()` | ❌ no |
| `Object.getOwnPropertySymbols()` | ✅ yes — the only way to explicitly retrieve them |
| `Reflect.ownKeys()` | ✅ yes (includes both strings and Symbols) |

This makes Symbols useful for attaching metadata to an object without it showing up in normal iteration, serialization, or accidentally colliding with a user-supplied property name.

## 4. Well-Known Symbols

JavaScript itself uses Symbols to let objects hook into language-level behavior:

```js
class Range {
  constructor(start, end) { this.start = start; this.end = end; }
  [Symbol.iterator]() { /* makes this object work with for...of, see Iterators topic */ }
}

class Money {
  constructor(amount) { this.amount = amount; }
  [Symbol.toPrimitive](hint) {
    if (hint === "number") return this.amount;
    if (hint === "string") return `$${this.amount.toFixed(2)}`;
    return `Money(${this.amount})`;
  }
}
const price = new Money(9.5);
console.log(+price);         // 9.5 — hint "number"
console.log(`${price}`);     // "$9.50" — hint "string"
```

`Symbol.iterator`, `Symbol.toPrimitive`, `Symbol.hasInstance`, and `Symbol.toStringTag` are all "well-known symbols" that customize built-in language operations for your own classes.

## 5. `Symbol.for()` — the Global Symbol Registry

```js
const a = Symbol.for("shared-key");
const b = Symbol.for("shared-key");
console.log(a === b); // true — Symbol.for reuses the SAME symbol for the same string, across the entire runtime
```

Unlike `Symbol("x")` (always unique), `Symbol.for("x")` looks up (or creates) a shared Symbol in a global registry — useful when multiple, independently-loaded modules need to agree on the same Symbol key.

## 6. Symbols Are Not Enumerable by JSON

```js
const obj = { name: "Ana", [Symbol("secret")]: "hidden" };
console.log(JSON.stringify(obj)); // {"name":"Ana"} — Symbol-keyed data is silently dropped
```

This is a deliberate design choice: Symbols are meant for in-memory metadata, not serialized data.

## Under the Hood: Why Symbols Never Collide With String Keys

### Separate key spaces, not just "different-looking" keys

It's tempting to think of a Symbol key as just "a weird string that's hard to guess" — but internally, engines maintain **genuinely separate storage** for string-keyed and Symbol-keyed properties on an object's hidden class (see the Objects and Prototypes topics). A Symbol is never coerced to a string for the purpose of key comparison, unlike numeric keys (`obj[1]` really is stored and compared as the string `"1"` internally). This is *why* `user[ID]` and `user["ID"]` in section 2 can never collide, even in principle — they're not just different values being compared, they're looked up through entirely different property-key mechanisms.

### Well-known symbols are just symbol-keyed properties on built-in prototypes

```js
console.log(typeof Array.prototype[Symbol.iterator]); // "function"
```

`Symbol.iterator` isn't special engine magic layered on top of objects — it's a genuine, engine-predefined `Symbol` value, and `Array.prototype[Symbol.iterator]` is a real Symbol-keyed property sitting on `Array.prototype`, found via the exact same prototype-chain lookup mechanism as any inherited method (Prototype Chain topic). Defining `[Symbol.iterator]` on your own class (Iterators topic) works for exactly the same reason a well-known symbol works on a built-in — `for...of` simply looks up whatever is stored at that Symbol key on the object's chain, without caring whether that Symbol came from the engine or from your own code.

### `Symbol.for()`'s registry is one real, engine-wide table

```js
Symbol.for("app:id"); // first call: registry has no entry for "app:id" -> creates one, stores it, returns it
Symbol.for("app:id"); // second call, ANY module, ANY time later: registry already has it -> returns the SAME symbol
```

The global symbol registry is a genuine key-value table maintained once per JavaScript realm (roughly: once per running engine instance/tab/worker), mapping string keys to their associated Symbol. This is exactly the mechanism that lets two independently-bundled libraries — which have no direct reference to each other's code — agree on the same Symbol key purely by using the same string with `Symbol.for()`, something two separate `Symbol("app:id")` calls could never achieve, since each `Symbol()` call always allocates a brand-new, structurally unique value with no registry lookup involved at all.

## 7. Common Pitfalls

1. **Expecting `Symbol("x") === Symbol("x")`** — they are always different values; only `Symbol.for("x")` provides equality across calls.
2. **Trying to use `new Symbol()`** — throws a `TypeError`; `Symbol` is a function, not a constructor, and must be called without `new`.
3. **Assuming Symbol-keyed properties are truly private** — `Object.getOwnPropertySymbols()` can still retrieve them; use `#private` class fields (see the Classes topic) for genuine access-restricted privacy.
4. **Forgetting `JSON.stringify` drops Symbol keys entirely** — a common surprise when debugging "missing" data after serialization.

## 8. Quick Reference

```
Symbol("desc")        → always unique, even with the same description
Symbol.for("key")      → shared/global, same value for the same key
obj[sym] = value        → hidden from Object.keys/for-in/JSON.stringify
Symbol.iterator          → well-known symbol powering for...of
```
