Symbols & Well-Known Symbols

📖 Concept

Symbol is a primitive type that creates unique, immutable identifiers. Every Symbol() call creates a completely unique value.

Use cases: Property keys that can't collide, defining protocols/interfaces, implementing well-known behaviors.

Well-Known Symbols are built-in Symbols that let you customize object behavior: Symbol.iterator, Symbol.toPrimitive, Symbol.toStringTag, Symbol.hasInstance, etc.

🏠 Real-world analogy: Symbols are like invisible ink labels. Two labels that say "id" in invisible ink are still different labels — even with the same description, each Symbol is unique.

💻 Code Example

codeTap to expand ⛶
1// Basic Symbols
2const id1 = Symbol("id");
3const id2 = Symbol("id");
4console.log(id1 === id2); // false (every Symbol is unique)
5console.log(typeof id1); // "symbol"
6
7// As property keys (no collisions!)
8const SECRET = Symbol("secret");
9const user = {
10 name: "Alice",
11 [SECRET]: "hidden-value"
12};
13console.log(user[SECRET]); // "hidden-value"
14console.log(Object.keys(user)); // ["name"] — Symbols are hidden!
15console.log(JSON.stringify(user)); // '{"name":"Alice"}' — excluded!
16
17// Global Symbol registry
18const s1 = Symbol.for("shared");
19const s2 = Symbol.for("shared");
20console.log(s1 === s2); // true (shared via registry)
21console.log(Symbol.keyFor(s1)); // "shared"
22
23// Well-known Symbols
24class Money {
25 constructor(amount, currency) {
26 this.amount = amount;
27 this.currency = currency;
28 }
29 // Custom string conversion
30 [Symbol.toPrimitive](hint) {
31 if (hint === "number") return this.amount;
32 if (hint === "string") return `${this.currency}${this.amount}`;
33 return this.amount;
34 }
35 // Custom toString tag
36 get [Symbol.toStringTag]() {
37 return "Money";
38 }
39}
40const price = new Money(42, "$");
41console.log(+price); // 42
42console.log(`${price}`); // "$42"
43console.log(Object.prototype.toString.call(price)); // "[object Money]"
44
45// Symbol.iterator (make custom objects iterable)
46class Range {
47 constructor(start, end) {
48 this.start = start;
49 this.end = end;
50 }
51 [Symbol.iterator]() {
52 let current = this.start;
53 const end = this.end;
54 return {
55 next() {
56 return current <= end
57 ? { value: current++, done: false }
58 : { done: true };
59 }
60 };
61 }
62}
63console.log([...new Range(1, 5)]); // [1, 2, 3, 4, 5]

🏋️ Practice Exercise

Mini Exercise:

  1. Create a Symbol-based private property pattern for a class
  2. Implement Symbol.iterator to make a linked list iterable
  3. Use Symbol.toPrimitive to make a custom class work with arithmetic
  4. Create a plugin system where plugins register via Symbol keys

⚠️ Common Mistakes

  • Symbols are NOT strings — Symbol('id') and 'id' are completely different

  • Symbols are invisible to for...in, Object.keys(), and JSON.stringify — use Object.getOwnPropertySymbols()

  • Symbol() vs Symbol.for()Symbol() always creates a new one; Symbol.for() uses a global registry

  • Can't convert Symbol to a string implicitly — '' + symbol throws TypeError; use symbol.toString() explicitly

  • Symbols are not truly private — they're just non-enumerable. Object.getOwnPropertySymbols() exposes them

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for Symbols & Well-Known Symbols. Login to unlock this feature.