DocsHub
ES6+

Symbols

Learn what Symbols are, why they exist, and how to use them in JavaScript.

Symbols

A Symbol is a primitive value that is guaranteed to be unique. Every time you create a Symbol, you get a value that is completely different from every other Symbol — even if they have the same description.

const id1 = Symbol("id");
const id2 = Symbol("id");

console.log(id1 === id2); // false — always unique
console.log(typeof id1);  // "symbol"

The string "id" is just a description — a label to help you identify the Symbol when debugging. It has no effect on uniqueness.


Why Symbols Exist

JavaScript objects use strings as property keys. The problem — if two pieces of code add a property with the same string key to the same object, they overwrite each other.

const user = { id: 1, name: "Ali" };

// Library A adds a property
user.meta = { source: "libraryA" };

// Library B also adds a property with the same name
user.meta = { source: "libraryB" }; // ❌ overwrites libraryA's property

Symbols solve this. Because every Symbol is unique, using a Symbol as a property key guarantees no collision — ever.

const metaA = Symbol("meta");
const metaB = Symbol("meta");

user[metaA] = { source: "libraryA" };
user[metaB] = { source: "libraryB" };

console.log(user[metaA]); // { source: "libraryA" }
console.log(user[metaB]); // { source: "libraryB" }
// Both coexist — no collision

Creating Symbols

// Basic symbol — description is optional but helpful
const sym = Symbol();
const namedSym = Symbol("description");

// Print a symbol — shows its description
console.log(namedSym.toString()); // "Symbol(description)"
console.log(namedSym.description); // "description"

Symbols cannot be converted to strings automatically — this prevents accidental use:

const sym = Symbol("mySymbol");

console.log("Symbol is: " + sym); // ❌ TypeError — cannot convert to string
console.log(`Symbol is: ${sym}`); // ❌ TypeError — same

// Convert explicitly when you need to
console.log(sym.toString()); // "Symbol(mySymbol)"
console.log(String(sym));    // "Symbol(mySymbol)"

Using Symbols as Object Keys

Use bracket notation to use a Symbol as a property key.

const ID = Symbol("id");
const CREATED_AT = Symbol("createdAt");

const user = {
  name: "Ali",
  [ID]: 1,
  [CREATED_AT]: new Date()
};

console.log(user.name);      // Ali
console.log(user[ID]);       // 1
console.log(user[CREATED_AT]); // Date object

Symbol properties are hidden from most iteration

Symbol-keyed properties do not show up in for...in, Object.keys(), Object.values(), or Object.entries(). They are effectively invisible to normal enumeration.

const ID = Symbol("id");

const user = {
  name: "Ali",
  age: 22,
  [ID]: 1
};

console.log(Object.keys(user));   // ["name", "age"] — no Symbol
console.log(Object.values(user)); // ["Ali", 22] — no Symbol

for (const key in user) {
  console.log(key); // name, age — no Symbol
}

To access Symbol properties specifically:

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

// Get everything — including symbols
const allKeys = [
  ...Object.keys(user),
  ...Object.getOwnPropertySymbols(user)
];
console.log(allKeys); // ["name", "age", Symbol(id)]

This hidden nature makes Symbols useful for metadata and internal implementation details that should not be exposed to regular code.


Global Symbol Registry — Symbol.for()

Sometimes you want to share a Symbol across different parts of your code — different files, different modules. Create a global Symbol with Symbol.for().

// Creates a symbol in the global registry — or returns existing one
const sym1 = Symbol.for("shared");
const sym2 = Symbol.for("shared");

console.log(sym1 === sym2); // true — same symbol from registry

Compare with regular Symbol():

const local1 = Symbol("local");
const local2 = Symbol("local");
console.log(local1 === local2); // false — always different

const global1 = Symbol.for("global");
const global2 = Symbol.for("global");
console.log(global1 === global2); // true — same from registry

Look up the key of a global symbol:

const sym = Symbol.for("auth");
console.log(Symbol.keyFor(sym)); // "auth"

const localSym = Symbol("local");
console.log(Symbol.keyFor(localSym)); // undefined — not in registry

Use Symbol.for() when you need the same Symbol across modules. Use Symbol() when you want a unique Symbol local to one file.


Well-Known Symbols

JavaScript has a set of built-in Symbols called well-known symbols. They let you customize how objects behave with built-in JavaScript operations.

You already saw one — Symbol.iterator — which makes objects iterable with for...of.

Symbol.iterator — make an object iterable

class Range {
  constructor(from, to) {
    this.from = from;
    this.to = to;
  }

  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;

    return {
      next() {
        if (current <= last) {
          return { value: current++, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
}

const range = new Range(1, 5);

for (const num of range) {
  console.log(num); // 1, 2, 3, 4, 5
}

console.log([...range]); // [1, 2, 3, 4, 5]

Symbol.toPrimitive — control type conversion

Called when JavaScript tries to convert an object to a primitive value.

class Money {
  constructor(amount, currency) {
    this.amount = amount;
    this.currency = currency;
  }

  [Symbol.toPrimitive](hint) {
    if (hint === "number") return this.amount;
    if (hint === "string") return `${this.amount} ${this.currency}`;
    return this.amount; // default
  }
}

const price = new Money(1200, "PKR");

console.log(`Price: ${price}`);  // Price: 1200 PKR — string hint
console.log(price * 2);          // 2400 — number hint
console.log(price + 500);        // 1700 — default hint

Symbol.hasInstance — customize instanceof

class EvenNumber {
  static [Symbol.hasInstance](value) {
    return typeof value === "number" && value % 2 === 0;
  }
}

console.log(4 instanceof EvenNumber);  // true
console.log(7 instanceof EvenNumber);  // false
console.log(12 instanceof EvenNumber); // true

Symbol.toStringTag — customize Object.prototype.toString

class Collection {
  get [Symbol.toStringTag]() {
    return "Collection";
  }
}

const col = new Collection();
console.log(Object.prototype.toString.call(col)); // "[object Collection]"

A Practical Use — Non-Colliding Object Metadata

// A library that tracks metadata about DOM elements
const METADATA = Symbol("elementMetadata");

function attachMetadata(element, data) {
  element[METADATA] = {
    ...data,
    attachedAt: Date.now()
  };
}

function getMetadata(element) {
  return element[METADATA];
}

const btn = document.querySelector("button");

attachMetadata(btn, {
  purpose: "submit-form",
  trackingId: "btn-001"
});

console.log(getMetadata(btn));
// { purpose: "submit-form", trackingId: "btn-001", attachedAt: 1710000000000 }

// The metadata is invisible to regular code
console.log(Object.keys(btn)); // no METADATA key visible

This is a common pattern in libraries — attach hidden metadata to objects without risking collision with user code or other libraries.


Symbols in Real Codebases

You will not create Symbols every day — but you will encounter them:

  • Symbol.iterator — whenever you make something iterable
  • Symbol.for() — when sharing identifiers across modules
  • Well-known symbols — when customizing object behavior for built-in operations
  • Library internals — many libraries use Symbols to store private state on user objects

Symbols are not used for security. Symbol properties can still be accessed with Object.getOwnPropertySymbols(). They are for uniqueness and collision prevention — not for hiding sensitive data.


Summary

  • A Symbol is a guaranteed unique primitive value — no two Symbols are ever equal
  • Created with Symbol("description") — the description is just a label for debugging
  • Use Symbols as object property keys to avoid naming collisions
  • Symbol properties are hidden from for...in, Object.keys(), and Object.entries()
  • Access Symbol properties with Object.getOwnPropertySymbols()
  • Symbol.for("key") creates or retrieves a Symbol from the global registry — shared across modules
  • Symbol.keyFor(sym) retrieves the key of a global Symbol
  • Well-known symbols customize built-in JavaScript behavior — Symbol.iterator, Symbol.toPrimitive, Symbol.hasInstance, Symbol.toStringTag
  • Use Symbols for non-colliding metadata, unique identifiers, and customizing language behavior

On this page