Skip to main content

Symbol in JavaScript

Symbol is a primitive data type introduced in ES6 that represents a unique and immutable value. Every call to Symbol() creates a new unique value even when the description is the same, which is why symbols are used as property keys that are guaranteed not to clash with anything.

Theory

TL;DR

  • Symbol() creates a unique immutable value; two symbols with the same description are not equal.
  • The main use is unique object property keys with no risk of a name collision.
  • Symbol keys do not appear in for...in, Object.keys() or JSON.stringify().
  • They can still be retrieved with Object.getOwnPropertySymbols(), so they are "hidden" rather than private fields.
  • Well-known symbols (Symbol.iterator, Symbol.toPrimitive and others) change the behaviour of built-in operations.
  • Symbol.for(key) takes a symbol from the global registry, so such symbols can be compared across modules.

Quick example

javascript
const id1 = Symbol('user'); const id2 = Symbol('user'); console.log(id1 === id2); // false const ID = Symbol('id'); const user = { name: 'Tim', [ID]: 1 }; console.log(user[ID]); // 1 console.log(Object.keys(user)); // ["name"]

What Symbol is and why it is unique

A symbol is created by calling the function:

javascript
const id = Symbol();

Every call to Symbol() creates a new unique value, even when the descriptions match:

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

Although both symbols carry the same description 'user', they are different. The description is only a debugging hint, it has no effect on equality.

What Symbol is for

Symbols are most often used as unique object property keys, to avoid name collisions.

Ordinary keys do collide:

javascript
const user = { name: 'Tim' }; user.id = 1; user.id = 2; // overwritten

With a symbol that cannot happen:

javascript
const ID = Symbol('id'); const user = { name: 'Tim', [ID]: 1 }; console.log(user[ID]); // 1

Now the [ID] property is:

  • unique;
  • invisible during ordinary property enumeration.

Symbols do not take part in ordinary enumeration

javascript
for (let key in user) { console.log(key); } // "name"
javascript
console.log(Object.keys(user)); // ["name"]

A symbol does not show up in for...in, Object.keys() or JSON.stringify(). But it can still be obtained:

javascript
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)]

Well-known symbols

JavaScript has built-in symbols, special values that let you change the behaviour of built-in operations.

SymbolWhat it is used for
Symbol.iteratormakes an object iterable (for...of)
Symbol.toPrimitivecustomises conversion to a primitive
Symbol.toStringTagsets the string returned by Object.prototype.toString
Symbol.hasInstanceoverrides the behaviour of instanceof
Symbol.asyncIteratorenables for await...of

An example with Symbol.iterator:

javascript
const arr = [1, 2, 3]; const iterator = arr[Symbol.iterator](); console.log(iterator.next()); // { value: 1, done: false }

It is Symbol.iterator that makes an array "iterable", and that is exactly what makes for...of and spread (...arr) work.

An example with Symbol.toPrimitive:

javascript
const user = { name: 'Tim', age: 25, [Symbol.toPrimitive](hint) { return hint === 'string' ? this.name : this.age; } }; console.log(String(user)); // "Tim" console.log(+user); // 25

The symbol lets you control how an object is converted to a number or a string.

The global registry and "hidden" keys

Sometimes you need a symbol with a given name to be the same symbol everywhere. That is what the global symbol registry is for:

javascript
const id1 = Symbol.for('user'); const id2 = Symbol.for('user'); console.log(id1 === id2); // true

Symbol.for(key) looks the symbol up in the global registry and, when it is not found, creates a new one and stores it under that key. Symbol.keyFor() does the reverse:

javascript
Symbol.keyFor(id1); // 'user'

Symbols are a good fit for:

  • creating "private" properties in libraries and classes;
  • extending objects you do not own without risking their data;
  • metadata, for example in frameworks or an ORM.

An example of use in a class:

javascript
const _id = Symbol('id'); class User { constructor(name) { this.name = name; this[_id] = Math.random(); } } const user = new User('Tim'); console.log(user); // { name: 'Tim', [Symbol(id)]: 0.123... } console.log(Object.keys(user)); // ["name"]

The symbol makes the _id field invisible to ordinary iteration and protects it from accidental overwriting.

The summary table:

PropertyDescription
Typea primitive
Uniquenessevery Symbol() is unique
Used asa key for object properties
Not visiblein for...in, Object.keys(), JSON.stringify()
Global registrythrough Symbol.for() and Symbol.keyFor()
Examples of built-in symbolsSymbol.iterator, Symbol.toPrimitive, Symbol.hasInstance and others

Common mistakes

  • Expecting Symbol('id') === Symbol('id'). The description does not make symbols equal, every call produces a new symbol.
  • Treating symbol fields as genuinely private. They are visible through Object.getOwnPropertySymbols() and Reflect.ownKeys(); for real privacy there are #privateField declarations.
  • Calling Symbol with new. That is a TypeError, because Symbol is a primitive, not a constructor.
  • Concatenating a symbol with a string ('id: ' + sym). The implicit conversion throws a TypeError, you need String(sym) or sym.description.
  • Confusing Symbol() with Symbol.for(). The first is always unique, the second shares a value through the global registry.
  • Counting on symbol fields to survive JSON.stringify() or structuredClone(). They simply disappear.

Short Answer

Interview ready
Premium

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