The Symbol type in JavaScript
Symbol is a unique and immutable value that can be used as an object property key. Every Symbol is unique, even when its description is identical to another symbol's.
Theory
TL;DR
Symbolis a primitive type, alongsidestring,number,booleanand the others.- Every call to
Symbol('id')creates a new unique value. - A symbol can be used as a property key:
{ [sym]: value }. - Symbol keys are invisible to
for...in,Object.keys()andJSON.stringify(). - You read them with
Object.getOwnPropertySymbols(). Symbol.for()gives a shared global symbol,Symbol.keyFor()returns its name.- Well known symbols (
Symbol.iterator,Symbol.toPrimitiveand others) change how objects behave.
Quick example
const id1 = Symbol('id');
const id2 = Symbol('id');
console.log(id1 === id2); // falseDespite the identical description 'id', the symbols are not equal. Every Symbol is a unique identifier, and the description exists only for debugging convenience.
Why Symbol exists
Before symbols, object property keys could only be strings. Sometimes you need to add a "hidden" property to an object so that it does not disturb the rest of the code, for example third party libraries. That is exactly where Symbol helps.
const id = Symbol('id');
const user = {
name: 'Alex',
[id]: 123, // a symbol as the key
};
console.log(user);
// { name: 'Alex', [Symbol(id)]: 123 }
console.log(user[id]); // 123
console.log(Object.keys(user)); // ['name'], the symbol is not listedSymbol keys take no part in for...in or Object.keys() and do not clash with other properties. That is handy for internal data or "private" properties.
const TOKEN = Symbol('token');
const session = {
user: 'Tim',
[TOKEN]: 'secret-token-123',
};
console.log(session.user); // Tim
console.log(session[TOKEN]); // secret-token-123
console.log(Object.keys(session)); // ['user']In other words, the [TOKEN] property does exist, but it is "invisible" to most operations. There is a dedicated method for reading symbol properties:
const symbols = Object.getOwnPropertySymbols(session);
console.log(symbols); // [Symbol(token)]
console.log(session[symbols[0]]); // secret-token-123Symbol compared with other types used as keys:
| Type | Can be an object key | Unique | Appears in for...in |
|---|---|---|---|
string | Yes | No | Yes |
number | No, it is converted to a string | No | Yes, as a string |
Symbol | Yes | Yes | No |
A Symbol is never coerced to a string implicitly
const sym = Symbol('id');
console.log('My symbol: ' + sym); // TypeErrorTo print a symbol as a string, do it explicitly:
console.log(sym.toString()); // Symbol(id)
console.log(String(sym)); // Symbol(id)
console.log(sym.description); // idGlobal symbols: Symbol.for and Symbol.keyFor
Sometimes the very same Symbol has to be used in different parts of a program. The global symbol registry exists for that:
const a = Symbol.for('shared');
const b = Symbol.for('shared');
console.log(a === b); // trueSymbol.for(key)creates a new global symbol or returns the existing one.Symbol.keyFor(sym)returns the name the symbol is registered under:
console.log(Symbol.keyFor(a)); // 'shared'For an ordinary Symbol('id') created outside the registry, Symbol.keyFor() returns undefined.
Well known symbols
JavaScript defines several built in symbols that let you override how objects behave:
| Symbol | What it is used for |
|---|---|
Symbol.iterator | Makes an object iterable (for...of) |
Symbol.toPrimitive | Controls conversion of an object to a primitive |
Symbol.toStringTag | Defines the name used by Object.prototype.toString |
Symbol.hasInstance | Defines the behaviour of the instanceof operator |
Symbol.species | Controls the constructor used for derived objects |
Symbol.asyncIterator | For asynchronous iterators |
A Symbol.iterator example:
const numbers = {
data: [1, 2, 3],
[Symbol.iterator]() {
let i = 0;
const arr = this.data;
return {
next() {
return i < arr.length
? { value: arr[i++], done: false }
: { done: true };
}
};
}
};
for (const n of numbers) {
console.log(n); // 1, 2, 3
}Thanks to Symbol.iterator, a plain object became iterable.
When to use it in practice
Use Symbol when:
- you need a unique identifier that will not collide with other properties;
- you want to add a "private" property to an object or a class;
- you want to tune object behaviour through well known symbols (
Symbol.iterator,Symbol.toPrimitiveand so on).
Short summary:
| What | Description |
|---|---|
| Type | Primitive (symbol) |
| Main trait | Uniqueness |
| Creation | Symbol('description') |
| Object keys | Can serve as a key, unique and hidden |
| Global symbols | Symbol.for() and Symbol.keyFor() |
| Well known symbols | Let you override JS behaviour |
Common mistakes
- Calling
Symbolwithnew.new Symbol('id')throws aTypeError: it is a primitive, not a constructor. - Comparing symbols by description.
Symbol('id') === Symbol('id')is alwaysfalse; onlySymbol.for('id')gives a shared symbol. - Concatenating a symbol with a string implicitly.
'x' + symthrows aTypeError, you need an explicitString(sym)orsym.toString(). - Treating symbol properties as truly private. They are only hidden from ordinary enumeration, while
Object.getOwnPropertySymbols()andReflect.ownKeys()reveal them. - Expecting symbol keys in JSON.
JSON.stringify()silently drops them, so data that must be serialised should not be hidden behind a symbol. - Forgetting the square brackets.
{ sym: 1 }creates an ordinary string property'sym'rather than a symbol key; the correct form is{ [sym]: 1 }.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.