SKU // 98

JavaScript Symbols

A Symbol is a primitive value that is guaranteed to be unique. No two Symbols are ever equal, even if they have the same description. Their main job is to be object keys that can never clash with any other key, and they are how JavaScript itself adds hidden hooks like Symbol.iterator.

// Creating Symbols

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

console.log(a === b);        // false  every Symbol is unique
console.log(typeof a);       // "symbol"
console.log(a.description);   // "id"   the label is just for humans
false symbol id
No new
new Symbol() throws a TypeError. Symbols are primitives, so you call Symbol() directly, like String() or Number().

The description is optional. Without one, description is undefined, and every Symbol() is still unique:

const x = Symbol();
const y = Symbol();
console.log(x === y, x.description, String(x));
false undefined Symbol()

// Symbols as Object Keys

Use a Symbol inside square brackets to create a property no other code can accidentally overwrite:

const ID = Symbol("id");

const user = {
  name: "Ava",
  id: "from-server",     // a normal string key
  [ID]: 1042              // a separate, symbol key
};

console.log(user.id);     // "from-server"
console.log(user[ID]);    // 1042   no collision
from-server 1042

This matters when you attach your own data to objects that other code also uses. A string key like "id" might already exist; your Symbol key cannot clash with anything.

// Symbol Keys Are Hidden From Most Tools

Object.keys(user)                     // ["name", "id"]
JSON.stringify(user)                  // '{"name":"Ava","id":"from-server"}'
for (const k in user) { }              // visits name and id only

Object.getOwnPropertySymbols(user)    // [Symbol(id)]  you have to ask specifically
["name", "id"] {"name":"Ava","id":"from-server"} [Symbol(id)]
Hidden, Not Secret
Symbol keys are skipped by normal loops and JSON, which keeps them out of the way. But they are not private: anyone who asks with getOwnPropertySymbols can see them. For true privacy, use closures, WeakMaps, or #private class fields.

// Shared Symbols: Symbol.for()

Sometimes you want the same Symbol in different files. Symbol.for(key) looks it up in a global registry, creating it only the first time:

Symbol.for("app.theme") === Symbol.for("app.theme")   // true
Symbol("app.theme") === Symbol("app.theme")           // false

// Well-Known Symbols

JavaScript has built-in Symbols that let your objects plug into language features. You already used one:

Symbol.iteratorMakes an object work with for...of and spread (JS Iterators)
Symbol.toPrimitiveControls how an object turns into a number or string
Symbol.toStringTagSets the name shown by Object.prototype.toString
Symbol.asyncIteratorLike Symbol.iterator, for data that arrives over time (for await...of; not covered in this course)
const money = {
  cents: 1999,
  [Symbol.toPrimitive](hint) {
    return hint === "number" ? this.cents / 100 : `$${(this.cents / 100).toFixed(2)}`;
  }
};

console.log(`Total: ${money}`);   // "Total: $19.99"
console.log(+money * 2);         // 39.98
Total: $19.99 39.98

// Well-Known Symbols in Action

JS Iterators showed that for...of works on anything with a [Symbol.iterator] method. Here is W3Schools' version: a plain object that loops over its own data:

const myObject = {
  data: ["A", "B", "C"],
  [Symbol.iterator]() {
    let index = 0;
    const data = this.data;
    return {
      next() {
        if (index < data.length) {
          const value = data[index];
          index++;
          return { value: value, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

for (const x of myObject) console.log(x);
A B C

Symbol.toStringTag changes the name that Object.prototype.toString reports (the kind trick from Object Types):

const box = { [Symbol.toStringTag]: "Box" };
console.log(String(box));
[object Box]

// Symbols and Strings

const s = Symbol("tag");

"Key: " + s            // TypeError: Cannot convert a Symbol value to a string
"Key: " + String(s)    // "Key: Symbol(tag)"
s.description            // "tag"
TypeError: Cannot convert a Symbol value to a string Key: Symbol(tag) tag
Trap: template literals throw too
`Key: ${s}` is the same error as "Key: " + s: TypeError: Cannot convert a Symbol value to a string. Inside a template, use String(s) or s.description. (console.log(s) is fine: it shows Symbol(tag).)

// Should You Use Symbols?

Rule of Thumb
In everyday app code, rarely. You will mostly meet Symbols through the well-known ones like Symbol.iterator. Reach for your own when you add properties to objects you do not control and need a guarantee they will never clash.

// Quick Check

What does this log?

console.log(Symbol("a") === Symbol("a"), Symbol.for("a") === Symbol.for("a"));
Show answer
false true. Every Symbol() call makes a new, unique symbol. Symbol.for looks the key up in the global registry, so both calls return the same symbol.

const id = Symbol("id"); and const user = { name: "Ava", [id]: 7 }; What is Object.keys(user)?

  1. ["name", "id"]
  2. ["name"]
  3. ["name", Symbol(id)]
  4. []
Show answer
B. Symbol keys are skipped by Object.keys, for...in, and JSON.stringify. Use Object.getOwnPropertySymbols to find them.

Which is correct, new Symbol("x") or Symbol("x")? Why?

Show answer
Symbol("x"). Symbols are primitives, so there is no wrapper to construct: new Symbol("x") throws TypeError: Symbol is not a constructor.

// Try It

  1. Click Open Playground and run it: two Symbols with the same description. Why are they not equal?
  2. Add a Symbol key to an object, then check Object.keys and JSON.stringify
  3. Find the Symbol key again with Object.getOwnPropertySymbols
  4. Give an object a Symbol.toPrimitive method so `${obj}` prints something friendly
// Video Walkthroughs
Last updated: Oct 10, 2026, 1:47 AM ET