Symbols
Symbol primitive type, well-known symbols, and use cases
What are Symbols?
Symbol is a primitive type introduced in ES6 that represents a unique identifier.
Unlike strings, every Symbol is guaranteed to be unique, making them perfect for property keys
that won't collide with other properties.
Key Characteristics
- Unique — Every Symbol() call creates a unique symbol
- Immutable — Cannot be changed once created
- Not enumerable — Hidden from for...in and Object.keys()
- Description — Optional string for debugging purposes
Creating Symbols
// Create a symbol
const sym1 = Symbol();
const sym2 = Symbol();
console.log(sym1 === sym2); // false — always unique
// Symbol with description (for debugging)
const id = Symbol("id");
console.log(id.toString()); // "Symbol(id)"
console.log(id.description); // "id"
// Same description, still different symbols
const a = Symbol("name");
const b = Symbol("name");
console.log(a === b); // false
// ⚠️ Cannot use 'new'
new Symbol(); // TypeError: Symbol is not a constructor
Symbols as Property Keys
const ID = Symbol("id");
const SECRET = Symbol("secret");
const user = {
name: "Alice",
[ID]: 12345,
[SECRET]: "password123"
};
// Access with symbol
console.log(user[ID]); // 12345
console.log(user[SECRET]); // "password123"
// Symbol properties are hidden from normal enumeration
console.log(Object.keys(user)); // ["name"]
console.log(JSON.stringify(user)); // {"name":"Alice"}
for (const key in user) {
console.log(key); // Only "name"
}
// But can be accessed with:
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id), Symbol(secret)]
console.log(Reflect.ownKeys(user)); // ["name", Symbol(id), Symbol(secret)]
Symbol.for() - Global Registry
// Create/retrieve from global symbol registry
const globalSym1 = Symbol.for("app.id");
const globalSym2 = Symbol.for("app.id");
console.log(globalSym1 === globalSym2); // true — same symbol!
// Get the key for a global symbol
console.log(Symbol.keyFor(globalSym1)); // "app.id"
// Regular symbols aren't in the registry
const localSym = Symbol("local");
console.log(Symbol.keyFor(localSym)); // undefined
// Use case: Share symbols across modules/iframes
// In module A:
const SHARED_KEY = Symbol.for("myApp.sharedKey");
// In module B:
const key = Symbol.for("myApp.sharedKey");
// key === SHARED_KEY
Well-Known Symbols
JavaScript has built-in symbols that customize object behavior:
// Symbol.iterator - Make objects iterable
const range = {
start: 1,
end: 5,
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
}
return { done: true };
}
};
}
};
console.log([...range]); // [1, 2, 3, 4, 5]
// Symbol.toStringTag - Customize Object.prototype.toString
class MyClass {
get [Symbol.toStringTag]() {
return "MyClass";
}
}
console.log(Object.prototype.toString.call(new MyClass()));
// "[object MyClass]"
// Symbol.toPrimitive - Control type conversion
const money = {
amount: 100,
currency: "USD",
[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 USD"
console.log(money + 50); // 150
More Well-Known Symbols
// Symbol.hasInstance - Customize instanceof
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof MyArray); // true
console.log({} instanceof MyArray); // false
// Symbol.species - Constructor for derived objects
class MyArray extends Array {
static get [Symbol.species]() {
return Array; // map(), filter() return regular Arrays
}
}
const arr = new MyArray(1, 2, 3);
const mapped = arr.map(x => x * 2);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
// Symbol.isConcatSpreadable
const arr1 = [1, 2];
const notSpreadable = {
0: 3,
1: 4,
length: 2,
[Symbol.isConcatSpreadable]: false
};
console.log(arr1.concat(notSpreadable)); // [1, 2, {0: 3, 1: 4, ...}]
notSpreadable[Symbol.isConcatSpreadable] = true;
console.log(arr1.concat(notSpreadable)); // [1, 2, 3, 4]
Practical Use Cases
// 1. Private-like properties (before #private syntax)
const _balance = Symbol("balance");
class BankAccount {
constructor(initial) {
this[_balance] = initial;
}
deposit(amount) {
this[_balance] += amount;
}
getBalance() {
return this[_balance];
}
}
const account = new BankAccount(100);
console.log(account.getBalance()); // 100
console.log(account._balance); // undefined
console.log(Object.keys(account)); // []
// 2. Avoid property collision in mixins
const flyable = (() => {
const FLY_SPEED = Symbol("flySpeed");
return {
[FLY_SPEED]: 100,
fly() {
console.log(`Flying at ${this[FLY_SPEED]} mph`);
}
};
})();
// 3. Type branding
const StringType = Symbol("String");
const NumberType = Symbol("Number");
function branded(value, type) {
return { value, [type]: true };
}
📚 Learn More
const str = branded("hello", StringType);
console.log(str[StringType]); // true
console.log(str[NumberType]); // undefined
💡 Key Takeaways
- • Symbol() creates unique, immutable identifiers
- • Use symbols for property keys to avoid collisions
- • Symbol.for() creates shared global symbols
- • Well-known symbols (Symbol.iterator, etc.) customize object behavior
- • Symbol properties are hidden from normal enumeration
- • Great for libraries and frameworks to avoid conflicts