← thecodex.expert · The Codex Family of Knowledge
The Codex / JavaScript / Symbols

Symbols

JavaScript's unique identifier primitive — Symbol(), well-known symbols, Symbol.iterator, Symbol.for, and using symbols as private-ish object keys.

What it is, in plain English: A Symbol is a primitive value that is guaranteed to be unique — no two Symbols are ever equal, even if created with the same description. Symbols are used as object property keys that can't accidentally collide with other keys. Well-known Symbols (Symbol.iterator, Symbol.toPrimitive, Symbol.hasInstance...) let you hook into JavaScript's built-in behaviours.
Where this bites you

Symbol keys are hidden from Object.keys, for...in and JSON.stringify, which makes them look private. They aren't. Object.getOwnPropertySymbols() and Reflect.ownKeys() list them, and Node's console.log prints them along with their values. Keep an API key under a Symbol and the first debug log of that object puts it in your logs.

const API_KEY = Symbol("apiKey");
const account = { name: "Alice", [API_KEY]: "sk-live-1234" };

console.log(Object.keys(account));        // looks hidden...
console.log(account);                     // ...until someone logs the object
console.log(Object.getOwnPropertySymbols(account).map(s => account[s]));
// [ 'name' ]
// { name: 'Alice', [Symbol(apiKey)]: 'sk-live-1234' }
// [ 'sk-live-1234' ]

Unique keys that never collide

JavaScript objects can have properties keyed by strings or Symbols. String keys can clash — if two pieces of code both add a "id" property, one overwrites the other. A Symbol is guaranteed unique — even two Symbols created with the same description are different values.

Creating Symbols

const sym1 = Symbol("description");   // the description is just for debugging
const sym2 = Symbol("description");

console.log(sym1 === sym2);   // false — ALWAYS different
console.log(typeof sym1);     // "symbol"
console.log(sym1.description); // "description" — the label you gave it
console.log(sym1.toString());  // "Symbol(description)"

// You cannot new Symbol() — it's not a constructor:
// new Symbol()  // TypeError: Symbol is not a constructor

Using Symbols as object keys

const SECRET = Symbol("secret");
const ID     = Symbol("id");

const user = {
  name:     "Alice",
  [SECRET]: "password123",   // computed property syntax for Symbol keys
  [ID]:     42,
};

// Access with the Symbol reference:
console.log(user[SECRET]);   // "password123"
console.log(user[ID]);       // 42
console.log(user.name);      // "Alice"

// Symbol keys are hidden from normal enumeration:
console.log(Object.keys(user));       // ["name"] — no symbols
console.log(JSON.stringify(user));    // {"name":"Alice"} — symbols dropped

// You need the actual Symbol reference to access it:
// user["secret"]  → undefined (that's a string key, not the Symbol)

Symbols prevent name collisions. They don't control access. When a value must not be readable from outside, use a class private field (#name). It's enforced by the language, it isn't listed by any reflection method, and Node's console.log doesn't print it:

class Account {
  #apiKey;                                // private field: unreachable outside the class
  constructor(name, apiKey) {
    this.name = name;
    this.#apiKey = apiKey;
  }
  authHeader() { return `Bearer ${this.#apiKey}`; }
}

const account = new Account("Alice", "sk-live-1234");
console.log(account);
console.log(Object.getOwnPropertySymbols(account));
console.log(account.authHeader());
// Account { name: 'Alice' }
// []
// Bearer sk-live-1234

The main uses of Symbols

// 1. Unique constants (no collision with other code):
const STATUS_PENDING  = Symbol("pending");
const STATUS_DONE     = Symbol("done");
const STATUS_FAILED   = Symbol("failed");

function process(task) {
  task.status = STATUS_PENDING;
  // ... do work ...
  task.status = STATUS_DONE;
}

// 2. Adding metadata to objects you don't own:
const META = Symbol("meta");
const thirdPartyObject = { data: [1, 2, 3] };
thirdPartyObject[META] = { addedAt: Date.now() };  // won't clash with any existing key

// 3. The global registry — share a Symbol across modules:
const shared1 = Symbol.for("app.user.id");
const shared2 = Symbol.for("app.user.id");
console.log(shared1 === shared2);  // true — same symbol from registry
Try It Yourself

Official Sources

The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.