Symbol is a primitive type introduced in ES6. Every Symbol is unique — even if two Symbols have the same description, they’re not equal. The main use case is creating property keys that are guaranteed to never collide with anything else.
Creating Symbols
const s1 = Symbol();
const s2 = Symbol();
console.log(s1 === s2); // false (every Symbol is unique)
// Description is just a label for debugging
const id = Symbol('id');
console.log(id.toString()); // 'Symbol(id)'
console.log(id.description); // 'id'
Note that we don’t use new Symbol() — Symbol is a primitive, not an object.
Symbols as Object Keys
This is the main use case. When we use a Symbol as a key, it won’t collide with any string key or any other Symbol key.
const ID = Symbol('id');
const user = {
name: 'Manish',
[ID]: 12345 // Symbol key — uses computed property syntax
};
console.log(user[ID]); // 12345
console.log(user.name); // 'Manish'
This is useful in libraries — if we add a Symbol property to a user’s object, we won’t accidentally overwrite any of their existing properties.
Symbols Are Not Enumerable
Symbols don’t show up in for...in, Object.keys(), or JSON.stringify(). This makes them great for “hidden” metadata properties.
const secret = Symbol('secret');
const obj = { visible: true, [secret]: 'hidden value' };
console.log(Object.keys(obj)); // ['visible']
console.log(JSON.stringify(obj)); // '{"visible":true}'
for (let key in obj) console.log(key); // 'visible'
// But we CAN access them if we know how
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(secret)]
Symbol.for() — Global Symbol Registry
Symbol.for() creates a Symbol in a global registry. If a Symbol with that key already exists, it returns the same one. This is how we share Symbols across files or modules.
const s1 = Symbol.for('app.id');
const s2 = Symbol.for('app.id');
console.log(s1 === s2); // true (same Symbol from registry)
// Regular Symbol() always creates a new one
const s3 = Symbol('app.id');
console.log(s1 === s3); // false
Well-Known Symbols
JavaScript has built-in Symbols that let us customize how objects behave with language features. The most important ones:
Symbol.iterator
Makes an object iterable (usable in for...of loops and spread syntax).
const range = {
from: 1,
to: 3,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { done: true };
}
};
}
};
console.log([...range]); // [1, 2, 3]
for (const n of range) console.log(n); // 1, 2, 3
Symbol.toPrimitive
Controls how an object is converted to a primitive value.
const money = {
amount: 100,
currency: 'INR',
[Symbol.toPrimitive](hint) {
if (hint === 'number') return this.amount;
if (hint === 'string') return `${this.amount} ${this.currency}`;
return this.amount; // default
}
};
console.log(+money); // 100
console.log(`${money}`); // '100 INR'
console.log(money + 50); // 150
In simple language, Symbols are like guaranteed-unique IDs. We use them when we need a property key that can never accidentally clash with anything else — and the well-known symbols let us hook into JavaScript’s own behavior.