JavaScript - JavaScript Symbols and Well-Known Symbols

1. Introduction

JavaScript provides several built-in data types for storing and manipulating information. Most developers are familiar with strings, numbers, booleans, objects, and arrays. However, JavaScript also includes a special primitive data type called Symbol.

A Symbol is used to create unique identifiers. Unlike strings or numbers, every newly created Symbol is unique, even when two Symbols have the same description.

Symbols are particularly useful when working with object properties, custom iteration, metaprogramming, and advanced JavaScript libraries.

This chapter explains JavaScript Symbols and Well-Known Symbols in a detailed, student-friendly manner.

2. What Is a Symbol in JavaScript?

A Symbol is a primitive data type introduced in ECMAScript 2015 (ES6).

A Symbol represents a unique value that can be used as an identifier, especially as a property key in an object.

Syntax

JavaScript

const symbolName = Symbol();

You can also provide a description:

JavaScript

const symbolName = Symbol("description");

The description helps developers understand the purpose of the Symbol. It does not determine its identity.

Example

JavaScript

const firstSymbol = Symbol("student");
const secondSymbol = Symbol("student");

console.log(firstSymbol === secondSymbol);

Output:

false

Explanation

Although both Symbols have the description "student", they are different values.

  • firstSymbol contains one unique Symbol.

  • secondSymbol contains another unique Symbol.

  • The equality operator returns false.

This is the fundamental property of Symbols.

3. Why Are Symbols Needed?

Before Symbols were introduced, developers commonly used strings as object property names.

Consider the following example:

JavaScript

const student = {
    name: "Rahul",
    age: 21
};

console.log(student.name);

Here, name and age are string-based property keys.

In larger applications, different developers or libraries may use the same property name. This can lead to accidental overwriting.

Problem with String Property Names

JavaScript

const student = {};

student.id = 101;

student.id = 202;

console.log(student.id);

Output:

202

The second assignment replaces the first value.

Using Symbols to Avoid Property Name Collisions

JavaScript

const student = {};

const firstId = Symbol("id");
const secondId = Symbol("id");

student[firstId] = 101;
student[secondId] = 202;

console.log(student[firstId]);
console.log(student[secondId]);

Output:

101
202

Explanation

The two Symbols represent separate property keys.

Even though both descriptions are "id", they do not conflict.

This is useful when:

  • Developing reusable libraries.

  • Adding internal properties to objects.

  • Avoiding conflicts between independent modules.

  • Creating custom JavaScript frameworks.

  • Defining special object behavior.

4. Creating Symbols

There are different ways to create and use Symbols in JavaScript.

4.1 Creating an Empty Symbol

JavaScript

const mySymbol = Symbol();

console.log(typeof mySymbol);

Output:

symbol

The typeof operator returns "symbol".

4.2 Creating a Symbol with a Description

JavaScript

const userSymbol = Symbol("user");

console.log(userSymbol.description);

Output:

user

The description property provides the description supplied during creation.

4.3 Symbols with the Same Description

JavaScript

const symbolA = Symbol("test");
const symbolB = Symbol("test");

console.log(symbolA === symbolB);

Output:

false

Descriptions are for identification and debugging. They do not make Symbols equal.

4.4 Symbols Cannot Be Created Using new

The Symbol constructor is not used with the new keyword.

Incorrect:

JavaScript

const mySymbol = new Symbol("test");

This throws a TypeError.

Correct:

JavaScript

const mySymbol = Symbol("test");

The Symbol() function creates a primitive Symbol value directly.

5. Using Symbols as Object Property Keys

One of the most important uses of Symbols is creating unique object property keys.

Example

JavaScript

const nameKey = Symbol("name");

const student = {
    [nameKey]: "Anita",
    age: 20
};

console.log(student[nameKey]);

Output:

Anita

Explanation

The square brackets are important:

JavaScript

[nameKey]

They tell JavaScript to use the value stored in nameKey as the property key.

Without square brackets:

JavaScript

const student = {
    nameKey: "Anita"
};

The property name would be the literal string "nameKey" rather than the Symbol stored in the variable.

Adding a Symbol Property After Object Creation

JavaScript

const student = {};

const rollNumber = Symbol("rollNumber");

student[rollNumber] = 45;

console.log(student[rollNumber]);

Output:

45

Accessing Symbol Properties

You can access Symbol properties using bracket notation:

JavaScript

const key = Symbol("score");

const result = {};

result[key] = 95;

console.log(result[key]);

Output:

95

Dot notation does not work with a Symbol variable:

JavaScript

console.log(result.key);

This looks for a property named "key", not the Symbol stored in key.

6. Symbols and Object Property Collisions

Symbols are useful when multiple parts of an application add properties to the same object.

Example Without Symbols

JavaScript

const employee = {
    name: "Kiran"
};

employee.status = "Active";

employee.status = "Inactive";

console.log(employee.status);

Output:

Inactive

The second property assignment overwrites the first.

Example with Symbols

JavaScript

const employee = {
    name: "Kiran"
};

const departmentStatus = Symbol("status");
const accountStatus = Symbol("status");

employee[departmentStatus] = "Active";
employee[accountStatus] = "Inactive";

console.log(employee[departmentStatus]);
console.log(employee[accountStatus]);

Output:

Active
Inactive

The two properties are independent.

Practical Use

Suppose a library adds an internal property to a user object:

JavaScript

const internalKey = Symbol("internal");

const user = {
    name: "Ravi"
};

user[internalKey] = {
    loggedIn: true
};

Another library can use its own Symbol without accidentally overwriting the first library's property.

7. Symbol Properties Are Not Normally Included in Common Enumeration

Symbol properties behave differently from ordinary string properties.

Consider:

JavaScript

const id = Symbol("id");

const student = {
    name: "Meena",
    age: 22,
    [id]: 101
};

console.log(Object.keys(student));

Output:

["name", "age"]

Explanation

Object.keys() returns enumerable own string-keyed properties. It does not include Symbol-keyed properties.

The Symbol property still exists.

JavaScript

console.log(student[id]);

Output:

101

Checking Whether a Symbol Property Exists

JavaScript

console.log(Object.getOwnPropertySymbols(student));

Output:

[Symbol(id)]

Object.getOwnPropertySymbols() returns an array of the object's own Symbol property keys.

Important Difference

|
Method

|

Returns Symbol properties?

|
| --- | --- |
|

Object.keys()

|

No

|
|

Object.values()

|

No

|
|

Object.entries()

|

No

|
|

for...in

|

No

|
|

Object.getOwnPropertySymbols()

|

Yes

|
|

Reflect.ownKeys()

|

Yes, along with string keys

|

Symbol properties are not completely invisible. They are simply excluded from many ordinary string-key enumeration methods.

8. Symbol Properties and for...in

The for...in loop iterates over enumerable string-keyed properties.

Example

JavaScript

const secretKey = Symbol("secret");

const user = {
    name: "Arun",
    age: 25,
    [secretKey]: "Private Data"
};

for (const key in user) {
    console.log(key);
}

Output:

name
age

The Symbol property is not displayed.

However, you can retrieve it separately:

JavaScript

console.log(user[secretKey]);

Output:

Private Data

Important Note

Symbols are not a security mechanism. A Symbol-keyed property can still be discovered using reflection methods if the Symbol key is accessible or obtained from the object.

9. The Global Symbol Registry

JavaScript provides a global Symbol registry through:

JavaScript

Symbol.for()

This method creates or retrieves a shared Symbol associated with a string key.

Syntax

JavaScript

Symbol.for("key");

Example

JavaScript

const first = Symbol.for("user");
const second = Symbol.for("user");

console.log(first === second);

Output:

true

Explanation

When Symbol.for("user") is called:

  1. JavaScript checks whether a Symbol is already registered under "user".

  2. If one exists, it returns that Symbol.

  3. Otherwise, it creates a new Symbol and registers it.

Therefore, both variables refer to the same Symbol.

Difference Between Symbol() and Symbol.for()

JavaScript

const a = Symbol("user");
const b = Symbol("user");

console.log(a === b);

Output:

false

Using the registry:

JavaScript

const c = Symbol.for("user");
const d = Symbol.for("user");

console.log(c === d);

Output:

true

Retrieving the Registry Key

Use:

JavaScript

Symbol.keyFor()

Example:

JavaScript

const registeredSymbol = Symbol.for("employee");

console.log(Symbol.keyFor(registeredSymbol));

Output:

employee

Important Difference

JavaScript

const localSymbol = Symbol("employee");

console.log(Symbol.keyFor(localSymbol));

Output:

undefined

Only registered Symbols created through Symbol.for() have a retrievable registry key.

10. What Are Well-Known Symbols?

Well-Known Symbols are built-in Symbol values provided by JavaScript.

They allow developers to customize how objects behave with built-in language operations.

For example, they can control:

  • How an object is iterated.

  • How an object is converted to a primitive value.

  • How an object behaves with instanceof.

  • How an object responds to certain operators.

  • How objects work with built-in methods such as Array.from().

Well-Known Symbols are accessed through the Symbol object.

Example

JavaScript

Symbol.iterator

This is a built-in Symbol used to define the default iterator for an object.

Another example:

JavaScript

Symbol.toPrimitive

This is used to customize primitive conversion.

These Symbols are standardized parts of JavaScript.

11. Important Well-Known Symbols

The following are important Well-Known Symbols for JavaScript development.

|
Well-Known Symbol

|

Purpose

|
| --- | --- |
|

Symbol.iterator

|

Defines the default iterator for an object

|
|

Symbol.asyncIterator

|

Defines the default asynchronous iterator

|
|

Symbol.toPrimitive

|

Controls conversion of an object to a primitive

|
|

Symbol.toStringTag

|

Customizes the tag returned by Object.prototype.toString

|
|

Symbol.hasInstance

|

Customizes the behavior of the instanceof operator

|
|

Symbol.isConcatSpreadable

|

Controls whether an object is spread by Array.prototype.concat()

|
|

Symbol.species

|

Controls the constructor used by certain derived objects

|
|

Symbol.match

|

Defines matching behavior for String.prototype.match()

|
|

Symbol.replace

|

Defines replacement behavior for String.prototype.replace()

|
|

Symbol.search

|

Defines search behavior for String.prototype.search()

|
|

Symbol.split

|

Defines splitting behavior for String.prototype.split()

|
|

Symbol.matchAll

|

Defines behavior for String.prototype.matchAll()

|
|

Symbol.unscopables

|

Controls which properties are excluded from with environment lookup

|

The most commonly studied Well-Known Symbols are Symbol.iterator, Symbol.toPrimitive, Symbol.toStringTag, and Symbol.hasInstance.

12. Symbol.iterator

Symbol.iterator is used to define an object's default iterator.

Objects that are iterable can be used with:

  • for...of

  • Spread syntax (...)

  • Array.from()

  • Other APIs that consume iterables

Arrays and strings already have built-in iterators.

Example with an Array

JavaScript

const numbers = [10, 20, 30];

for (const number of numbers) {
    console.log(number);
}

Output:

10
20
30

The array is iterable because it provides a method under Symbol.iterator.

Checking the Iterator

JavaScript

const numbers = [10, 20, 30];

const iterator = numbers[Symbol.iterator]();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

Output:

{ value: 10, done: false }
{ value: 20, done: false }
{ value: 30, done: false }
{ value: undefined, done: true }

Explanation

The iterator's next() method returns an object with two properties:

  • value: The current value.

  • done: Indicates whether iteration has completed.

When done becomes true, iteration is finished.

13. Creating a Custom Iterable Object

You can use Symbol.iterator to make your own object work with for...of.

Example

JavaScript

const student = {
    name: "Anita",
    age: 21,

    *[Symbol.iterator]() {
        yield this.name;
        yield this.age;
    }
};

for (const value of student) {
    console.log(value);
}

Output:

Anita
21

Explanation

The object contains a generator method assigned to Symbol.iterator.

JavaScript

*[Symbol.iterator]()

This method produces values one at a time using yield.

The for...of loop automatically uses the iterator.

Important Point

An ordinary object is not iterable by default:

JavaScript

const student = {
    name: "Anita",
    age: 21
};

for (const value of student) {
    console.log(value);
}

This causes a TypeError because