A symbol is a unique, primitive value usable as a property key. Because two symbols are never equal, they create collision-proof keys, and the built-in well-known symbols such as Symbol.iterator and Symbol.toPrimitive let an object customize how the language treats it.
Before you start
You should be comfortable with objects, property keys and the iterator protocol. This article explains symbol keys and the well-known hooks; it does not enumerate every well-known symbol.
Step-by-step walkthrough
Step 1: Create a unique key
const tag = Symbol('tag') returns a fresh, unique value; the description is only for debugging. Two Symbol('tag') calls produce different keys, so they never overwrite each other. This is ideal for metadata a library attaches without risking a name clash.
Step 2: Remember symbols are hidden from most enumeration
Symbol-keyed properties are skipped by JSON.stringify, for...in, Object.keys and Object.getOwnPropertyNames. Read them with Object.getOwnPropertySymbols or Reflect.ownKeys. If a value must serialize or be copied by spread, a symbol key is the wrong choice.
Step 3: Customize behavior with well-known symbols
Implement [Symbol.iterator] to make an object work with for...of and spread, and [Symbol.toPrimitive] to control coercion. These hooks let your type integrate with the language instead of requiring callers to convert manually.
Worked scenario
Run this with Node.js. The symbol key is invisible to ordinary enumeration.
const tag = Symbol('tag');
const obj = { [tag]: 'secret', visible: true };
console.log(Object.keys(obj)); // ['visible']
console.log(obj[tag]); // 'secret'
console.log(Object.getOwnPropertySymbols(obj).length); // 1Walk through the example
Object.keys returns only string keys, so visible appears and tag does not. The value is still readable when you hold the symbol, which is what makes symbols useful for private-ish metadata: callers who do not have the symbol cannot discover the key by accident. Symbols stored against a shared reference, such as Symbol.for('app.tag'), are findable across modules.
Common mistake
Expecting a symbol-keyed property to survive JSON.stringify or structuredClone. JSON omits symbol keys entirely, and the structured clone algorithm also drops symbol-keyed properties, so a value that must round-trip through storage should use string keys.
Verify the behavior
Assert that two symbols with the same description are not equal. Assert that a symbol key is absent from Object.keys, for...in and JSON.stringify, but present in Object.getOwnPropertySymbols. Compare Symbol() with Symbol.for('x') by calling Symbol.for('x') twice and confirming it returns the same value.
Interview exercise
A library wants to attach internal state to user objects without clashing with user fields. Should it use a symbol key or a WeakMap?
Answer and reasoning
Both work; choose based on visibility. A symbol key lives on the object, so anyone who obtains the symbol can read it and it travels with a spread of own enumerable properties, although it stays hidden from JSON. A WeakMap keeps the state fully private and lets the object be collected when no other references remain. Use a symbol for lightweight, object-owned metadata and a WeakMap when the association must not expose the object’s shape.
Continue learning
Compare the private-state approach in WeakMap and object lifetimes and read the MDN Symbol reference. Try the JavaScript interview questions.