Skip to main content

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

  • Symbol is a primitive type, alongside string, number, boolean and 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() and JSON.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.toPrimitive and others) change how objects behave.

Quick example

javascript
const id1 = Symbol('id'); const id2 = Symbol('id'); console.log(id1 === id2); // false

Despite 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.

javascript
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 listed

Symbol 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.

javascript
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:

javascript
const symbols = Object.getOwnPropertySymbols(session); console.log(symbols); // [Symbol(token)] console.log(session[symbols[0]]); // secret-token-123

Symbol compared with other types used as keys:

TypeCan be an object keyUniqueAppears in for...in
stringYesNoYes
numberNo, it is converted to a stringNoYes, as a string
SymbolYesYesNo

A Symbol is never coerced to a string implicitly

javascript
const sym = Symbol('id'); console.log('My symbol: ' + sym); // TypeError

To print a symbol as a string, do it explicitly:

javascript
console.log(sym.toString()); // Symbol(id) console.log(String(sym)); // Symbol(id) console.log(sym.description); // id

Global 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:

javascript
const a = Symbol.for('shared'); const b = Symbol.for('shared'); console.log(a === b); // true
  • Symbol.for(key) creates a new global symbol or returns the existing one.
  • Symbol.keyFor(sym) returns the name the symbol is registered under:
javascript
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:

SymbolWhat it is used for
Symbol.iteratorMakes an object iterable (for...of)
Symbol.toPrimitiveControls conversion of an object to a primitive
Symbol.toStringTagDefines the name used by Object.prototype.toString
Symbol.hasInstanceDefines the behaviour of the instanceof operator
Symbol.speciesControls the constructor used for derived objects
Symbol.asyncIteratorFor asynchronous iterators

A Symbol.iterator example:

javascript
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.toPrimitive and so on).

Short summary:

WhatDescription
TypePrimitive (symbol)
Main traitUniqueness
CreationSymbol('description')
Object keysCan serve as a key, unique and hidden
Global symbolsSymbol.for() and Symbol.keyFor()
Well known symbolsLet you override JS behaviour

Common mistakes

  • Calling Symbol with new. new Symbol('id') throws a TypeError: it is a primitive, not a constructor.
  • Comparing symbols by description. Symbol('id') === Symbol('id') is always false; only Symbol.for('id') gives a shared symbol.
  • Concatenating a symbol with a string implicitly. 'x' + sym throws a TypeError, you need an explicit String(sym) or sym.toString().
  • Treating symbol properties as truly private. They are only hidden from ordinary enumeration, while Object.getOwnPropertySymbols() and Reflect.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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.