Ch. 1 · JavaScript

JavaScript Symbols and Well-Known Symbols

Create unique property keys, customize object behavior with well-known symbols, and understand why symbols vanish from JSON and Object.keys.

~2 min readintermediateupdated Oct 5, 2026

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); // 1
JavaScript

Walk 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.

More in JavaScript

read ✓JavaScript · mid

JavaScript Date and Timezone Pitfalls

Parse and format dates without timezone surprises: zero-based months, date-only versus date-time parsing, and why UTC storage avoids drift.

~3 min readread →
read ✓JavaScript · mid

JavaScript Number Precision and BigInt

Why 0.1 + 0.2 is not 0.3, where safe integer range ends, and when BigInt is the right tool for large identifiers and money.

~2 min readread →
esc