Symbols & Well-Known Symbols
Reviewed & published by Brayan K
A Symbol is a unique, primitive value created with Symbol() that serves as a collision-proof object key and lets you hook into built-in language behaviors through well-known symbols like Symbol.iterator.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Symbols are how you add hidden, non-colliding metadata to objects and how you teach your own objects to work with for...of, spread, and type coercion.
What You'll Learn in This Lesson
- Create unique values with Symbol()
- Use symbols as non-colliding keys
- Share symbols via Symbol.for()
- Hide keys from loops & JSON
- Make objects iterable with Symbol.iterator
- Control coercion with Symbol.toPrimitive
📚 Prerequisites: You should know objects, object iteration, and the idea of primitive types. Symbols are a primitive type just like numbers and strings.
🔣 Real-World Analogy: A Symbol is like a numbered cloakroom ticket:
- • Two tickets can say "coat" (the description) but each is still a different ticket
- • Only the holder of the exact ticket can claim that coat — no accidental mix-ups
- • Symbol.for("vip") is a season pass: ask for the same name and you always get the same shared pass
1️⃣ Unique Values & Non-Colliding Keys
Symbol("desc") returns a value guaranteed to differ from every other symbol. The string you pass is only a description for debugging — two symbols with the same description are still not equal. Used as object keys, symbols never clash with string keys, and they're invisible to for...in, Object.keys, and JSON.stringify, which makes them ideal for private-ish metadata.
Not truly private: symbol keys are hidden from normal iteration but Object.getOwnPropertySymbols can still find them. Use them for de-facto privacy, not security.
const a = Symbol("id");
const b = Symbol("id");
console.log(a === b); // ✅ Expected output: false
const ID = Symbol("id");
const user = { name: "Ada", [ID]: 42 };
console.log(user[ID]); // ✅ Expected output: 42
console.log(Object.keys(user)); // ✅ Expected output: [ 'name' ]
console.log(JSON.stringify(user)); // ✅ Expected output: {"name":"Ada"}
// You can still find symbol keys explicitly
console.log(Object.getOwnPropertySymbols(user).length); // ✅ Expected output: 12️⃣ The Global Registry: Symbol.for & Symbol.keyFor
Plain Symbol() is private and unshareable. When you need the same symbol in different places, use Symbol.for("key"): it looks up a global registry, creating the symbol once and returning that identical symbol on every later call — even from another module. Symbol.keyFor recovers the registry key from such a symbol.
Namespace your keys (e.g. "myapp.userId") so registry symbols from different libraries don't accidentally collide on a common word.
// Same registry key → the exact same symbol
const s1 = Symbol.for("app.session");
const s2 = Symbol.for("app.session");
console.log(s1 === s2); // ✅ Expected output: true
// Plain Symbol() is never shared
console.log(Symbol("x") === Symbol("x")); // ✅ Expected output: false
// Recover the key
console.log(Symbol.keyFor(s1)); // ✅ Expected output: app.session
console.log(Symbol.keyFor(Symbol("z"))); // ✅ Expected output: undefined3️⃣ Well-Known Symbols: iterator & toPrimitive
JavaScript reserves a set of well-known symbols to customize language behavior. Define a [Symbol.iterator] method and your object suddenly works with for...of and the spread operator. Define [Symbol.toPrimitive] and you control how your object converts to a number or string in arithmetic and template literals.
Iterables power the language: arrays, strings, Maps, and Sets are all iterable because they implement Symbol.iterator. Now your objects can join the club.
🎯 Your Turn — A Hidden Key
Add a symbol key to the object so it stays out of Object.keys.
// 👉 replace ___ : create a symbol, then use it as a computed key
const secret = ___("secret");
const account = { owner: "Ada", [___]: "pin-1234" };
console.log(account[secret]); // ✅ Expected output: pin-1234
console.log(Object.keys(account)); // ✅ Expected output: [ 'owner' ]🧠 Quick Recall
Predict the output before revealing the answer.
false — every Symbol() call makes a new, unique value.
true — Symbol.for returns the same registry symbol for a given key.
{"a":2} — symbol keys are skipped by JSON.stringify.
Common Errors & Fixes
- 1️⃣ Expecting equal descriptions to mean equal symbols
- 2️⃣ Coercing a symbol to a string with +
- 3️⃣ Using a symbol as a key without brackets
- 4️⃣ Expecting symbol keys in Object.keys
🎯 Mini Challenge — Custom Iterable
Make a team object iterable so spreading it yields its member names.
const team = {
members: ["Ana", "Bo", "Cy"],
[Symbol.iterator]() {
let i = 0;
const list = this.members;
return {
next: () => (i < list.length ? { value: list[i++], done: false } : { value: undefined, done: true }),
};
},
};
console.log([...team]); // ✅ Expected output: [ 'Ana', 'Bo', 'Cy' ]
const [first] = team;
console.log(first); // ✅ Expected output: AnaQuick Reference
| Expression | Meaning |
|---|---|
| Symbol("d") | new unique value |
| Symbol.for("k") | shared registry symbol |
| { [sym]: v } | symbol as a key |
| Symbol.iterator | enables for...of / spread |
| Symbol.toPrimitive | controls coercion |
📚 Keep Learning
- MDN: Symbol reference
- MDN: Symbol.iterator
- MDN: Symbol.for()
🎉 Lesson Complete — Symbols!
- ✅ Symbol() creates a guaranteed-unique value
- ✅ Symbol keys never collide and are hidden from loops & JSON
- ✅ Symbol.for shares one symbol via the global registry
- ✅ Symbol.iterator makes objects work with for...of and spread
- ✅ Symbol.toPrimitive customizes coercion
Practice quiz
What is logged? console.log(Symbol('id') === Symbol('id'));
- true
- undefined
- false
- It throws
Answer: false. Every Symbol() call makes a brand-new unique value; the description does not affect identity.
What is the role of the string passed to Symbol('id')?
- It is just a description for debugging
- It makes equal symbols
- It registers the symbol globally
- It sets the symbol's value
Answer: It is just a description for debugging. The description is only a debugging label; two symbols with the same description are still unequal.
What does Symbol.for('app.id') === Symbol.for('app.id') evaluate to?
- false
- undefined
- It throws
- true
Answer: true. Symbol.for looks up a shared global registry, returning the same symbol for a given key every time.
How are symbol keys treated by Object.keys and JSON.stringify?
- They are included
- They are skipped/hidden
- They cause an error
- They are converted to strings
Answer: They are skipped/hidden. Symbol keys are invisible to for...in, Object.keys, and JSON.stringify — handy for de-facto private metadata.
Which method reveals symbol keys that Object.keys omits?
- Object.getOwnPropertySymbols
- Object.entries
- Object.getOwnPropertyNames
- Reflect.keys
Answer: Object.getOwnPropertySymbols. Object.getOwnPropertySymbols lists an object's symbol keys.
Defining a [Symbol.iterator] method on an object lets it work with:
- JSON.stringify
- Math operations
- for...of and the spread operator
- Object.freeze
Answer: for...of and the spread operator. Symbol.iterator is a well-known symbol that enables for...of and spread on your object.
What does Symbol.toPrimitive let you control?
- JSON serialization
- How the object converts to a number or string
- Garbage collection
- Property enumeration order
Answer: How the object converts to a number or string. Symbol.toPrimitive customizes coercion in arithmetic and template literals.
What is logged? console.log('id: ' + Symbol('x'));
- 'id: Symbol(x)'
- 'id: undefined'
- An empty string
- A TypeError
Answer: A TypeError. Implicitly coercing a symbol to a string with + throws; use .toString() or String() explicitly.
How do you use a symbol stored in a variable sym as an object key?
- { sym: 1 }
- { [sym]: 1 }
- { 'sym': 1 }
- { sym() : 1 }
Answer: { [sym]: 1 }. Use a computed key { [sym]: 1 }; { sym: 1 } would just use the string 'sym'.
What does Symbol.keyFor return for a symbol created with plain Symbol('z')?
- 'z'
- An empty string
- undefined
- It throws
Answer: undefined. Symbol.keyFor only finds registry symbols (from Symbol.for); a private Symbol() returns undefined.
Continue this course
- Previous: Object Methods (keys, entries, assign, freeze)
- Next: Getters, Setters & Property Descriptors — Computed and validated properties with get/set and defineProperty
- Quick reference: JavaScript cheat sheet
Frequently asked questions
What makes a Symbol unique?
Every call to Symbol() produces a brand-new value that is not equal to any other symbol — even one created with the same description. The description is just a label for debugging; it does not affect identity. This guaranteed uniqueness is the entire purpose of symbols.
When would I use a Symbol as an object key?
When you need to attach metadata to an object without risking a clash with existing or future string keys. Two libraries can each add their own symbol key to the same object and never collide. Symbol keys are also skipped by for...in, Object.keys, and JSON.stringify, so they stay out of the way.
What is the difference between Symbol() and Symbol.for()?
Symbol() always makes a fresh, private symbol. Symbol.for('id') looks up a shared symbol in a global registry by string key, creating it once and returning that same symbol every time afterward — even across different files or realms. Use Symbol.for when you need the same symbol everywhere.
What are well-known symbols?
They are built-in symbols like Symbol.iterator and Symbol.toPrimitive that JavaScript itself looks for to customize language behavior. Defining a Symbol.iterator method makes your object work with for...of and the spread operator; Symbol.toPrimitive controls how your object converts to a number or string.