🚀AdvancedIterators & Generators

Symbols & Well-Known Symbols

Use Symbol primitives as unique, collision-proof property keys and customise built-in JavaScript behaviour with well-known symbols.

Overview

Symbol is a primitive type introduced in ES2015. Every call to Symbol() produces a completely unique value no two symbols are ever equal, even if created with the same description. This uniqueness makes symbols ideal as property keys when you need to add metadata to an object without risking a name collision with any other code, library, or future JavaScript property. Beyond user-defined symbols, JavaScript defines a set of well-known symbols on the Symbol object itself: Symbol.iterator, Symbol.toPrimitive, Symbol.hasInstance, Symbol.toStringTag, Symbol.species, and others. These are hooks into the language runtime. By implementing a method at one of these keys on your object, you can customise how the object behaves in for-of loops, type coercion, instanceof checks, template literals, and more without touching any string-named property. Symbol.for(key) and Symbol.keyFor(sym) operate on a global symbol registry that persists across module boundaries and iframes. This is the mechanism used for cross-realm communication and allows different parts of an application to share the same symbol without passing it explicitly. Understanding the distinction between local symbols (Symbol()) and registered symbols (Symbol.for()) is critical when building libraries or micro-frontend architectures.

Key Points

  • •Symbol() always returns a unique value; two Symbol() calls with the same description are never ===
  • •Symbol descriptions (the string argument) are only for debugging they have no semantic meaning
  • •Symbol-keyed properties are not enumerable in for-in, Object.keys(), or JSON.stringify()
  • •Object.getOwnPropertySymbols() retrieves symbol-keyed properties; Reflect.ownKeys() returns all keys including symbols
  • •Symbol.for(key) looks up a global registry, creating the symbol only if it does not exist yet
  • •Well-known symbols customise built-in JS behaviour: Symbol.iterator, Symbol.toPrimitive, Symbol.toStringTag, Symbol.hasInstance
  • •Symbol.toPrimitive receives a hint ("number", "string", "default") and should return the appropriate primitive
  • •Symbols cannot be coerced to strings with + but can be explicitly converted with String(sym) or sym.toString()

Examples

Using Symbols as unique, non-colliding property keys

// Two libraries independently add metadata to the same object
const LIB_A_META = Symbol('lib-a-meta');
const LIB_B_META = Symbol('lib-b-meta');

const user = { name: 'Alice', age: 30 };
user[LIB_A_META] = { permissions: ['read', 'write'] };
user[LIB_B_META] = { theme: 'dark' };

// Normal enumeration does not see symbol keys
console.log(Object.keys(user));           // ['name', 'age']
console.log(JSON.stringify(user));        // {"name":"Alice","age":30}

// Retrieve symbol keys explicitly
console.log(Object.getOwnPropertySymbols(user));   // [Symbol(lib-a-meta), Symbol(lib-b-meta)]
console.log(user[LIB_A_META].permissions);         // ['read', 'write']

// Symbol uniqueness
const s1 = Symbol('tag');
const s2 = Symbol('tag');
console.log(s1 === s2); // false

Symbol keys are invisible to JSON.stringify and Object.keys, making them perfect for internal metadata.

Symbol.toPrimitive full control over type coercion

class Money {
  constructor(amount, currency) {
    this.amount = amount;
    this.currency = currency;
  }

  [Symbol.toPrimitive](hint) {
    if (hint === 'number') return this.amount;
    if (hint === 'string') return `${this.amount} ${this.currency}`;
    // 'default' hint used in + with strings or == comparisons
    return this.amount;
  }
}

const price = new Money(99.99, 'USD');

console.log(`Price: ${price}`);         // Price: 99.99 USD  (string hint)
console.log(price * 2);                  // 199.98           (number hint)
console.log(price + 0.01);              // 100              (default hint)

// Without Symbol.toPrimitive, all three would call .valueOf() or toString()

The hint argument tells you the context in which coercion is happening so you can return the most appropriate value.

Symbol.hasInstance and Symbol.toStringTag

// Symbol.hasInstance customise instanceof
class EvenNumber {
  static [Symbol.hasInstance](value) {
    return typeof value === 'number' && value % 2 === 0;
  }
}

console.log(4  instanceof EvenNumber); // true
console.log(7  instanceof EvenNumber); // false
console.log(12 instanceof EvenNumber); // true

// Symbol.toStringTag customise Object.prototype.toString output
class HttpResponse {
  get [Symbol.toStringTag]() {
    return 'HttpResponse';
  }
}

const resp = new HttpResponse();
console.log(Object.prototype.toString.call(resp));
// [object HttpResponse]

// Global symbol registry
const KEY = Symbol.for('app.config');
const same = Symbol.for('app.config');
console.log(KEY === same);           // true
console.log(Symbol.keyFor(KEY));     // 'app.config'

Symbol.hasInstance lets you define what it means to be an "instance of" your class, independent of prototype chains.

Common Mistakes

  • Trying to use a symbol as an object key with dot notation symbol keys always require bracket notation: obj[sym]
  • Assuming Symbol.for() is the same as Symbol() Symbol.for() uses a shared global registry and may return an existing symbol
  • Forgetting that symbol-keyed properties survive Object.assign and the spread operator ({...obj}) they are copied, just not enumerated
  • Using + to concatenate a symbol with a string this throws a TypeError; use String(sym) or template literals explicitly

Note

Use Symbol.for() to share symbols across ES modules without explicit imports two modules that both call Symbol.for("myapp.event") get the exact same symbol. This is how libraries can agree on a shared protocol without coupling to each other's exports.

Exercises

Write JavaScript code to solve each exercise. Run it and check your output.

Loading editor…

Completed: 0 / 5 exercises

Knowledge Check

Question 1 of 4

What is the result of `Symbol("foo") === Symbol("foo")`?