Topics

Jump to a topic.

Search questions

Find a question by title, tag, or summary.

What are Symbols for?

Questions in this chapter (7)
  • Draft
  • medium
  • Askedoften
  • Verified October 2026

Answer

A Symbol is a primitive whose only job is to be a unique property key. The spec: "Each Symbol is unique and immutable", and the Symbol type is the set of non-String values that can be used as property keys. Symbol('id') === Symbol('id') is false: the description is just a label, held in an immutable [[Description]] slot and readable via symbol.description.

What that buys you:

  • Collision-free keys. A library can add a property to an object it doesn't own without risking a clash with existing string keys or another library's symbols.
  • Hidden from the usual enumeration. Symbol-keyed properties don't appear in Object.keys, for…in or JSON.stringify. They're found with Object.getOwnPropertySymbols or Reflect.ownKeys. This is not privacy: anyone with the object can list them. Use #private fields for real encapsulation.
  • Language protocols. Well-known symbols such as Symbol.iterator, Symbol.hasInstance, Symbol.toPrimitive and Symbol.toStringTag are keys the engine looks up to customise for…of, instanceof, type conversion and Object.prototype.toString.

Global registry. Symbol(desc) is always a new symbol. Symbol.for(key) looks up key in a registry shared across the agent's realms and returns the existing symbol, creating one if none exists; Symbol.keyFor(symbol) goes the other way and returns undefined for a symbol that isn't registered. Use it only when separate pieces of code genuinely need the same symbol.

Rules. Call Symbol() without new; new Symbol() throws a TypeError. A symbol won't implicitly convert to a string (`${sym}` throws); use String(sym) or sym.description.

Example

const id = Symbol('id');
console.log(id === Symbol('id'), id.description, String(id)); // → false id Symbol(id)

const user = { name: 'Ada', [id]: 1 };
console.log(Object.keys(user), JSON.stringify(user)); // → [ 'name' ] {"name":"Ada"}
console.log(Object.getOwnPropertySymbols(user), Reflect.ownKeys(user));
// → [ Symbol(id) ] [ 'name', Symbol(id) ]
console.log({ ...user }[id]); // → 1

console.log(Symbol.for('app.id') === Symbol.for('app.id'), Symbol.keyFor(id)); // → true undefined

class Temperature {
  get [Symbol.toStringTag]() {
    return 'Temperature';
  }
}
console.log(String(new Temperature())); // → [object Temperature]

try {
  `${id}`;
} catch (error) {
  console.log(error.message); // → (V8 wording) Cannot convert a Symbol value to a string
}

Pitfalls

  • Calling symbol keys "private". They're only unlisted by default; Object.getOwnPropertySymbols and Reflect.ownKeys reveal them.
  • new Symbol(). It throws a TypeError (V8 words it "Symbol is not a constructor").
  • Expecting JSON.stringify or for…in to include symbol keys. They're skipped, so symbol data silently disappears when you serialise.
  • Using Symbol.for for ordinary uniqueness. Registry symbols are shared, so two unrelated modules choosing the same key get the same symbol.

Follow-ups

Do spread and Object.assign copy symbol keys?
Yes. Both copy own enumerable properties including symbol-keyed ones ({ ...user }[id] is 1 in the example), while Object.keys and JSON.stringify skip them.
What is Symbol.iterator?
A well-known symbol. for…of and spread look up that key on a value; an object with a method under it is iterable.