Symbols in JavaScript

· 5 min read · Updated March 11, 2026 · intermediate
symbols es6 primitives javascript

Symbols are a primitive data type introduced in ES6. Unlike strings or numbers, every Symbol is guaranteed to be unique. This makes them useful for creating object property keys that will never conflict with other keys.

Creating Symbols

Call Symbol() to create a new unique Symbol:

const sym1 = Symbol();
const sym2 = Symbol("my symbol");
const sym3 = Symbol("my symbol");

The string you pass is optional. It serves as a description for debugging purposes only. It does not affect uniqueness:

sym2 === sym3; // false

Each call to Symbol() creates a new value, even with the same description.

You cannot use new with Symbol:

const sym = new Symbol(); // TypeError: Symbol is not a constructor

This is intentional. Symbols are primitives, not objects. Use Object() if you need a wrapper:

const sym = Symbol("test");
const symObj = Object(sym);
typeof symObj; // "object"

Using Symbols as Property Keys

Assign a Symbol as an object property key:

const id = Symbol("id");
const user = {
  name: "Alice",
  [id]: 42
};

user.name;      // "Alice"
user[id];       // 42

Symbols as keys are excluded from Object.keys() and JSON.stringify(). They are hidden from common enumeration:

Object.keys(user);        // ["name"]
JSON.stringify(user);    // {"name":"Alice"}

To get all Symbol keys from an object, use Object.getOwnPropertySymbols():

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

This gives you a way to create truly private properties, though it is not foolproof since the symbols are still accessible.

The Global Symbol Registry

Sometimes you need the same Symbol across different files or modules. The global registry solves this:

const symA = Symbol.for("app.id");
const symB = Symbol.for("app.id");

symA === symB; // true

Symbol.for(key) checks the registry for an existing Symbol with that key. If found, it returns it. If not, it creates a new one.

To retrieve the key from a registered Symbol, use Symbol.keyFor():

const sym = Symbol.for("myKey");
Symbol.keyFor(sym); // "myKey"

Registered Symbols are not garbage collected. They persist for the lifetime of the program. This differs from non-registered Symbols, which can be garbage collected if no references to them exist.

Well-known Symbols

JavaScript defines several built-in Symbols that customize language behavior. These are static properties on the Symbol constructor and are called well-known Symbols.

Symbol.iterator

This is the most common one. Objects that implement Symbol.iterator become iterable in for...of loops:

const collection = {
  items: [1, 2, 3],
  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => {
        if (index < this.items.length) {
          return { value: this.items[index++] };
        }
        return { done: true };
      }
    };
  }
};

for (const item of collection) {
  console.log(item); // 1, 2, 3
}

Arrays, strings, and maps implement this Symbol by default.

Symbol.hasInstance

Customize how instanceof behaves:

class EvenNumbers {
  static [Symbol.hasInstance](num) {
    return Number.isInteger(num) && num % 2 === 0;
  }
}

42 instanceof EvenNumbers; // true
7 instanceof EvenNumbers;  // false

Symbol.toStringTag

Change the output of Object.prototype.toString():

const user = {
  [Symbol.toStringTag]: "User",
  name: "Alice"
};

Object.prototype.toString.call(user); // "[object User]"

This is how frameworks like React identify component types.

Symbol.species

Define a getter that returns a constructor for creating derived objects. Used by array methods:

class MyArray extends Array {
  static get [Symbol.species]() {
    return Array;
  }
}

const instance = MyArray.from([1, 2, 3]);
const mapped = instance.map(x => x * 2);
mapped instanceof MyArray; // false
mapped instanceof Array;   // true

Without the species override, map() would return another MyArray instance.

Symbol.match, Symbol.replace, Symbol.search, Symbol.split

These customize string methods behavior:

const formatter = {
  [Symbol.replace](str, replacement) {
    return str.toUpperCase() + " - " + replacement;
  }
};

"hello".replace(formatter, "world"); // HELLO - world

This lets objects behave like string pattern matchers.

Practical Use Cases

Unique Object Keys

Create object keys that will not clash with any other property:

const _private = Symbol("private");

class Container {
  constructor(value) {
    this[_private] = value;
  }

  getValue() {
    return this[_private];
  }
}

const box = new Container("secret");
box.getValue();           // "secret"
Object.keys(box);         // []

The underscore convention (_private) is just a convention. Symbol makes it technically inaccessible through normal property access.

Defining Constants

Use Symbols for enum-like constants where uniqueness matters:

const RED = Symbol("red");
const BLUE = Symbol("blue");
const GREEN = Symbol("green");

function setColor(color) {
  switch (color) {
    case RED:   // ...
    case BLUE:  // ...
  }
}

Unlike strings, you cannot accidentally pass the wrong Symbol since each is unique.

Avoiding Property Name Collisions

When extending built-in objects or working with third-party libraries, Symbol keys prevent conflicts:

const thirdPartyObj = { render: () => {} };

// Your custom method won't overwrite existing property
thirdPartyObj[Symbol("render")] = () => console.log("custom");

This is a safer approach than appending to the object directly.

Common Mistakes

Expecting Symbol(“x”) === Symbol(“x”)

They are always different. Use Symbol.for() if you need shared values:

Symbol("id") === Symbol("id");           // false
Symbol.for("id") === Symbol.for("id");   // true

Trying to Convert Symbols to Numbers

This throws a TypeError:

const sym = Symbol("test");
+sym;           // TypeError
sym | 0;        // TypeError

You can convert to string explicitly:

String(sym);    // "Symbol(test)"
sym.toString(); // "Symbol(test)"

Forgetting Symbols are Not Enumerable

If you iterate over an object expecting Symbol keys to appear, they will not:

const obj = { a: 1, [Symbol("b")]: 2 };

for (const key in obj) {
  console.log(key); // "a" only
}

Object.keys(obj);   // ["a"] only

You need Object.getOwnPropertySymbols() to access them.

See Also