JavaScript 52 🧬 Symbols
const sym1 = Symbol();
const sym2 = Symbol('description');
const sym3 = Symbol('description');
console.log(sym1);
console.log(sym2);
console.log(sym2 === sym3);
const obj = {
[sym1]: 'value1',
[sym2]: 'value2',
regular: 'regular'
};
console.log(obj[sym1]);
console.log(obj[sym2]);
console.log(Object.keys(obj));
const globalSym1 = Symbol.for('app.id');
const globalSym2 = Symbol.for('app.id');
console.log(globalSym1 === globalSym2);
console.log(Symbol.keyFor(globalSym1));
const iterable = {
[Symbol.iterator]() {
let i = 0;
return {
next: () => i < 3 ? { value: i++, done: false } : { done: true }
};
}
};
console.log([...iterable]);
console.log(Symbol.iterator);
console.log(Symbol.asyncIterator);
console.log(Symbol.hasInstance);
console.log(Symbol.toPrimitive);
console.log(Symbol.toStringTag);
A Symbol is a unique, immutable primitive value used as an object property key. Every Symbol is guaranteed to be different from every other Symbol — even if they have the same description. Symbols let you add properties to objects without any risk of collision, and they power many of JavaScript’s built-in protocols.
Key point: Symbols are hidden from normal enumeration. Object.keys, for...in, and JSON.stringify all skip them. This makes them perfect for metadata, internal state, and protocol hooks — invisible to code that doesn’t know to look.
a – What is a Symbol
A Symbol is a primitive type introduced in ES2015. It’s guaranteed unique — no two Symbols are ever equal, even with the same description.
Creating Symbols:
const sym1 = Symbol();
const sym2 = Symbol('description');
const sym3 = Symbol('description');
The string argument is just a description — for debugging. It doesn’t affect uniqueness.
Symbols are always unique:
console.log(Symbol() === Symbol());
// [ false ]
console.log(Symbol('a') === Symbol('a'));
// [ false ]
console.log(Symbol('a') === Symbol('b'));
// [ false ]
This is the whole point — Symbols never collide.
Symbols are primitives:
console.log(typeof Symbol());
// [ 'symbol' ]
console.log(Symbol() instanceof Object);
// [ false ]
Like strings, numbers, and booleans, Symbols are primitives — not objects.
The description:
const sym = Symbol('user.id');
console.log(sym.toString());
// [ 'Symbol(user.id)' ]
console.log(sym.description);
// [ 'user.id' ]
Symbol.description (ES2019) gives you the string. toString gives you the full form.
Symbols as object keys:
const id = Symbol('id');
const user = {
name: 'Alice',
[id]: 42
};
console.log(user.name);
// [ 'Alice' ]
console.log(user[id]);
// [ 42 ]
Use bracket notation for Symbol keys — dot notation doesn’t work.
Symbols are hidden from enumeration:
const id = Symbol('id');
const user = { name: 'Alice', [id]: 42 };
console.log(Object.keys(user));
// [ [ 'name' ] ]
console.log(Object.values(user));
// [ [ 'Alice' ] ]
console.log(Object.entries(user));
// [ [ [ 'name', 'Alice' ] ] ]
for (const key in user) {
console.log(key);
}
// [ 'name' ]
console.log(JSON.stringify(user));
// [ '{"name":"Alice"}' ]
Symbols are invisible to all the usual iteration and serialization methods.
To get Symbol keys, use explicit methods:
console.log(Object.getOwnPropertySymbols(user));
// [ [ Symbol(id) ] ]
console.log(Reflect.ownKeys(user));
// [ [ 'name', Symbol(id) ] ]
Object.getOwnPropertySymbols returns only Symbol keys. Reflect.ownKeys returns both string and Symbol keys.
Why Symbols matter:
| Use case | Why Symbols |
|---|---|
| Hidden properties | Not enumerable by default |
| Unique keys | No collision with strings |
| Metadata | Invisible to normal code |
| Protocol hooks | Symbol.iterator, etc. |
| Registry keys | Symbol.for shares across modules |
Symbol vs String keys:
| Feature | String key | Symbol key |
|---|---|---|
| Uniqueness | Collisions possible | Always unique |
| Enumeration | Object.keys includes | Hidden |
for...in | ✅ | ❌ |
JSON.stringify | ✅ | ❌ |
| Dot notation | ✅ | ❌ (brackets) |
| Copy with spread | ✅ | ✅ (own enumerable) |
Spread does copy Symbols:
const sym = Symbol('x');
const a = { [sym]: 1, name: 'Alice' };
const b = { ...a };
console.log(b[sym]);
// [ 1 ]
Spread copies own enumerable properties, including Symbols.
Common Symbol use case — preventing name collisions:
const metadata = Symbol('metadata');
function attach(obj, data) {
obj[metadata] = data;
}
function read(obj) {
return obj[metadata];
}
No risk of clobbering obj.metadata — that’s a different key entirely.
b – Global Symbols and well-known Symbols
JavaScript has two categories of Symbols — those you create, and those the language provides.
Global Symbols — Symbol.for:
Symbol.for(key) returns a shared Symbol from a global registry. Two calls with the same key return the same Symbol.
const a = Symbol.for('app.id');
const b = Symbol.for('app.id');
console.log(a === b);
// [ true ]
Unlike Symbol(), global Symbols are cached — this lets different modules share the same Symbol by name.
Symbol.keyFor — reverse lookup:
const sym = Symbol.for('app.id');
console.log(Symbol.keyFor(sym));
// [ 'app.id' ]
console.log(Symbol.keyFor(Symbol('local')));
// [ undefined ] ← local Symbols aren't in the registry
When to use Symbol.for:
- Sharing Symbols across modules without imports
- Interop between libraries
- Registering global identifiers
When to use Symbol():
- Local, private keys
- One-off properties
- Most cases
Well-known Symbols:
JavaScript defines a set of built-in Symbols that hook into language protocols. These are on the Symbol object.
| Symbol | Purpose |
|---|---|
Symbol.iterator | Iteration protocol |
Symbol.asyncIterator | Async iteration |
Symbol.hasInstance | instanceof behavior |
Symbol.toPrimitive | Type coercion |
Symbol.toStringTag | Custom toString |
Symbol.isConcatSpreadable | Spreadable in concat |
Symbol.species | Derived constructor |
Symbol.match | String.match behavior |
Symbol.replace | String.replace behavior |
Symbol.search | String.search behavior |
Symbol.split | String.split behavior |
Symbol.unscopables | Excluded properties |
Symbol.iterator — make anything iterable:
const range = {
from: 1,
to: 3,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { done: true };
}
};
}
};
console.log([...range]);
// [ [ 1, 2, 3 ] ]
for (const n of range) {
console.log(n);
}
// [ 1 ]
// [ 2 ]
// [ 3 ]
Implement Symbol.iterator and your object becomes iterable with for...of, spread, destructuring, etc.
Symbol.asyncIterator — async iteration:
const asyncRange = {
from: 1,
to: 3,
[Symbol.asyncIterator]() {
let current = this.from;
const last = this.to;
return {
async next() {
if (current <= last) {
return { value: current++, done: false };
}
return { done: true };
}
};
}
};
async function run() {
for await (const n of asyncRange) {
console.log(n);
}
}
run();
// [ 1 ]
// [ 2 ]
// [ 3 ]
Symbol.hasInstance — customize instanceof:
class Even {
static [Symbol.hasInstance](value) {
return typeof value === 'number' && value % 2 === 0;
}
}
console.log(2 instanceof Even);
// [ true ]
console.log(3 instanceof Even);
// [ false ]
Symbol.toPrimitive — control type coercion:
const obj = {
[Symbol.toPrimitive](hint) {
if (hint === 'number') return 42;
if (hint === 'string') return 'hello';
return 'default';
}
};
console.log(+obj);
// [ 42 ]
console.log(`${obj}`);
// [ 'hello' ]
console.log(obj + '');
// [ 'default' ]
Symbol.toStringTag — custom type string:
class MyClass {
get [Symbol.toStringTag]() {
return 'MyClass';
}
}
console.log(Object.prototype.toString.call(new MyClass()));
// [ '[object MyClass]' ]
Symbol.isConcatSpreadable — control concat:
const arr = [1, 2, 3];
arr[Symbol.isConcatSpreadable] = false;
console.log([0].concat(arr));
// [ [ 0, [ 1, 2, 3 ] ] ] ← not spread
Symbol.match, Symbol.replace, Symbol.search, Symbol.split:
These let a custom object act as a regex-like pattern in string methods:
const customPattern = {
[Symbol.match](str) {
return str.split('').reverse().join('');
}
};
console.log('hello'.match(customPattern));
// [ 'olleh' ]
This is advanced but powers things like third-party regex libraries.
Well-known Symbols table:
| Symbol | Where used |
|---|---|
Symbol.iterator | for...of, spread, destructuring |
Symbol.asyncIterator | for await...of |
Symbol.hasInstance | instanceof |
Symbol.toPrimitive | +, ${}, == |
Symbol.toStringTag | Object.prototype.toString |
Symbol.isConcatSpreadable | Array.prototype.concat |
Symbol.species | Derived constructors |
Symbol.match | String.prototype.match |
Symbol.replace | String.prototype.replace |
Symbol.search | String.prototype.search |
Symbol.split | String.prototype.split |
Symbol.unscopables | with statement |
c – Symbols in practice
Symbols are used in specific patterns. Here are the ones you’ll encounter in real code.
Pattern 1 — Hidden properties:
const metadata = Symbol('metadata');
class User {
constructor(name) {
this.name = name;
this[metadata] = { created: Date.now() };
}
}
const u = new User('Alice');
console.log(u.name);
// [ 'Alice' ]
console.log(Object.keys(u));
// [ [ 'name' ] ] ← metadata hidden
console.log(u[metadata].created > 0);
// [ true ]
Pattern 2 — Preventing property collisions:
const LIBRARY_KEY = Symbol('my-library');
class Widget {
constructor() {
this[LIBRARY_KEY] = { internal: true };
}
}
// User code can't accidentally overwrite
const w = new Widget();
w[LIBRARY_KEY] = null; // only if they have the Symbol
Pattern 3 — Enum-like constants:
const Colors = {
RED: Symbol('red'),
GREEN: Symbol('green'),
BLUE: Symbol('blue')
};
function paint(color) {
if (color === Colors.RED) return '#f00';
if (color === Colors.GREEN) return '#0f0';
if (color === Colors.BLUE) return '#00f';
}
console.log(paint(Colors.RED));
// [ '#f00' ]
Unlike strings, Symbols can’t be accidentally matched or guessed.
Pattern 4 — Implementing iterables:
class LinkedList {
constructor() {
this.head = null;
}
add(value) {
this.head = { value, next: this.head };
}
[Symbol.iterator]() {
let node = this.head;
return {
next() {
if (node) {
const value = node.value;
node = node.next;
return { value, done: false };
}
return { done: true };
}
};
}
}
const list = new LinkedList();
list.add(1);
list.add(2);
list.add(3);
console.log([...list]);
// [ [ 3, 2, 1 ] ]
Pattern 5 — Framework metadata:
const TYPE = Symbol('type');
function Entity(type) {
return function (target) {
target[TYPE] = type;
};
}
@Entity('user')
class User {}
console.log(User[TYPE]);
// [ 'user' ]
Used in decorator patterns, ORMs, and serialization libraries.
Pattern 6 — Well-known Symbol hooks:
class Temperature {
constructor(celsius) {
this.celsius = celsius;
}
[Symbol.toPrimitive](hint) {
if (hint === 'number') return this.celsius;
if (hint === 'string') return `${this.celsius}°C`;
return this.celsius;
}
}
const t = new Temperature(25);
console.log(+t);
// [ 25 ]
console.log(`${t}`);
// [ '25°C' ]
Pattern 7 — Brand checks:
const SECRET = Symbol('secret');
class Vault {
constructor(code) {
this[SECRET] = code;
}
reveal(token) {
if (token !== SECRET) throw new Error('Unauthorized');
return this[SECRET];
}
}
const vault = new Vault('1234');
console.log(vault.reveal(SECRET));
// [ '1234' ]
Only code with the Symbol can access the property.
Pattern 8 — Shared registry across modules:
// module-a.js
export const ID = Symbol.for('app.user.id');
// module-b.js
const ID = Symbol.for('app.user.id'); // same Symbol
const user = { [ID]: 42 };
// module-a.js can read it
console.log(user[ID]);
// [ 42 ]
Symbol.for lets different modules agree on the same key without importing.
Pattern 9 — Symbol.toStringTag:
class Matrix {
get [Symbol.toStringTag]() {
return 'Matrix';
}
}
console.log(Object.prototype.toString.call(new Matrix()));
// [ '[object Matrix]' ]
Pattern 10 — Checking for Symbols:
function isSymbol(x) {
return typeof x === 'symbol';
}
console.log(isSymbol(Symbol()));
// [ true ]
console.log(isSymbol('hello'));
// [ false ]
console.log(isSymbol(Symbol.iterator));
// [ true ]
Pattern 11 — Iterating Symbol keys:
const s1 = Symbol('a');
const s2 = Symbol('b');
const obj = { [s1]: 1, [s2]: 2, name: 'x' };
console.log(Object.getOwnPropertySymbols(obj));
// [ [ Symbol(a), Symbol(b) ] ]
for (const sym of Object.getOwnPropertySymbols(obj)) {
console.log(sym.description, obj[sym]);
}
// [ 'a' 1 ]
// [ 'b' 2 ]
Pattern 12 — Copying an object with Symbols:
const sym = Symbol('x');
const src = { [sym]: 1, name: 'Alice' };
const copy = { ...src };
console.log(copy[sym]);
// [ 1 ]
Spread copies enumerable own properties — including Symbols.
Pattern 13 — Reflect.ownKeys:
const sym = Symbol('x');
const obj = { [sym]: 1, name: 'Alice' };
console.log(Reflect.ownKeys(obj));
// [ [ 'name', Symbol(x) ] ]
Returns all own keys — strings and Symbols.
Pattern 14 — Symbol registry check:
function isGlobalSymbol(sym) {
return Symbol.keyFor(sym) !== undefined;
}
console.log(isGlobalSymbol(Symbol.for('x')));
// [ true ]
console.log(isGlobalSymbol(Symbol('x')));
// [ false ]
Pattern 15 — Symbols in Maps:
const sym = Symbol('key');
const map = new Map();
map.set(sym, 'value');
console.log(map.get(sym));
// [ 'value' ]
Maps accept Symbols as keys just like any value.
Common use cases summary:
| Use case | Pattern |
|---|---|
| Hidden data | Symbol key + non-enumerable |
| Unique IDs | Symbol() |
| Shared keys | Symbol.for |
| Custom iteration | [Symbol.iterator] |
| Type coercion | [Symbol.toPrimitive] |
| Custom instanceof | [Symbol.hasInstance] |
| Framework metadata | Symbol + decorators |
| Enum values | Object of Symbols |
Symbols vs private fields:
| Feature | Symbol | #private |
|---|---|---|
| Hiding | Convention | Truly private |
| Reflection | getOwnPropertySymbols | None |
| Shared | Via Symbol.for | No |
| Inheritance | Symbols inherit | Private fields don’t |
| Support | ES2015 | ES2022 |
| Use | Metadata, protocols | True encapsulation |
Symbols aren’t truly private — anyone with the Symbol can access them. For real privacy, use #private class fields.
Complete Example Session
// ============================================
// PART 1: CREATE SYMBOLS
// ============================================
const sym1 = Symbol();
const sym2 = Symbol('description');
const sym3 = Symbol('description');
console.log(typeof sym1);
// [ 'symbol' ]
console.log(sym2.toString());
// [ 'Symbol(description)' ]
console.log(sym2 === sym3);
// [ false ]
// ============================================
// PART 2: SYMBOL AS OBJECT KEY
// ============================================
const obj = {
[sym1]: 'value1',
[sym2]: 'value2',
regular: 'regular'
};
console.log(obj[sym1]);
// [ 'value1' ]
console.log(obj.regular);
// [ 'regular' ]
// ============================================
// PART 3: HIDDEN FROM ENUMERATION
// ============================================
console.log(Object.keys(obj));
// [ [ 'regular' ] ]
console.log(Object.getOwnPropertySymbols(obj).length);
// [ 2 ]
console.log(JSON.stringify(obj));
// [ '{"regular":"regular"}' ]
// ============================================
// PART 4: DESCRIPTION
// ============================================
console.log(sym2.description);
// [ 'description' ]
console.log(Symbol().description);
// [ undefined ]
// ============================================
// PART 5: GLOBAL SYMBOLS
// ============================================
const globalSym1 = Symbol.for('app.id');
const globalSym2 = Symbol.for('app.id');
console.log(globalSym1 === globalSym2);
// [ true ]
// ============================================
// PART 6: SYMBOL.KEYFOR
// ============================================
console.log(Symbol.keyFor(globalSym1));
// [ 'app.id' ]
console.log(Symbol.keyFor(Symbol('local')));
// [ undefined ]
// ============================================
// PART 7: SYMBOL.ITERATOR
// ============================================
const iterable = {
[Symbol.iterator]() {
let i = 0;
return {
next: () => i < 3 ? { value: i++, done: false } : { done: true }
};
}
};
console.log([...iterable]);
// [ [ 0, 1, 2 ] ]
for (const v of iterable) {
console.log(v);
}
// [ 0 ]
// [ 1 ]
// [ 2 ]
// ============================================
// PART 8: SYMBOL.HASINSTANCE
// ============================================
class Even {
static [Symbol.hasInstance](v) {
return typeof v === 'number' && v % 2 === 0;
}
}
console.log(2 instanceof Even);
// [ true ]
console.log(3 instanceof Even);
// [ false ]
// ============================================
// PART 9: SYMBOL.TOPRIMITIVE
// ============================================
const temp = {
celsius: 25,
[Symbol.toPrimitive](hint) {
if (hint === 'number') return this.celsius;
return `${this.celsius}°C`;
}
};
console.log(+temp);
// [ 25 ]
console.log(`${temp}`);
// [ '25°C' ]
// ============================================
// PART 10: SYMBOL.TOSTRINGTAG
// ============================================
class Matrix {
get [Symbol.toStringTag]() { return 'Matrix'; }
}
console.log(Object.prototype.toString.call(new Matrix()));
// [ '[object Matrix]' ]
// ============================================
// PART 11: WELL-KNOWN SYMBOLS
// ============================================
console.log(typeof Symbol.iterator);
// [ 'symbol' ]
console.log(typeof Symbol.asyncIterator);
// [ 'symbol' ]
console.log(typeof Symbol.hasInstance);
// [ 'symbol' ]
console.log(typeof Symbol.toPrimitive);
// [ 'symbol' ]
console.log(typeof Symbol.toStringTag);
// [ 'symbol' ]
// ============================================
// PART 12: REFLECT.OWNKEYS
// ============================================
const mixed = { name: 'Alice', [Symbol('id')]: 42 };
console.log(Reflect.ownKeys(mixed).length);
// [ 2 ]
// ============================================
// PART 13: SPREAD COPIES SYMBOLS
// ============================================
const source = { [Symbol('x')]: 1, name: 'Alice' };
const copy = { ...source };
console.log(Object.getOwnPropertySymbols(copy).length);
// [ 1 ]
// ============================================
// PART 14: ENUM-LIKE CONSTANTS
// ============================================
const Colors = {
RED: Symbol('red'),
GREEN: Symbol('green'),
BLUE: Symbol('blue')
};
function paint(c) {
if (c === Colors.RED) return '#f00';
if (c === Colors.GREEN) return '#0f0';
if (c === Colors.BLUE) return '#00f';
}
console.log(paint(Colors.RED));
// [ '#f00' ]
// ============================================
// PART 15: LINKED LIST ITERATOR
// ============================================
class LinkedList {
constructor() { this.head = null; }
add(v) { this.head = { v, next: this.head }; }
[Symbol.iterator]() {
let node = this.head;
return {
next() {
if (node) {
const v = node.v;
node = node.next;
return { value: v, done: false };
}
return { done: true };
}
};
}
}
const list = new LinkedList();
list.add(1);
list.add(2);
list.add(3);
console.log([...list]);
// [ [ 3, 2, 1 ] ]
// ============================================
// PART 16: HIDDEN PROPERTY
// ============================================
const SECRET = Symbol('secret');
class Vault {
constructor(code) { this[SECRET] = code; }
reveal(token) {
if (token !== SECRET) throw new Error('Unauthorized');
return this[SECRET];
}
}
const v = new Vault('1234');
console.log(v.reveal(SECRET));
// [ '1234' ]
// ============================================
// PART 17: TYPE CHECK
// ============================================
function isSymbol(x) {
return typeof x === 'symbol';
}
console.log(isSymbol(Symbol()));
// [ true ]
console.log(isSymbol('hello'));
// [ false ]
// ============================================
// PART 18: CUSTOM MATCH
// ============================================
const custom = {
[Symbol.match](str) {
return str.split('').reverse().join('');
}
};
console.log('hello'.match(custom));
// [ 'olleh' ]
// ============================================
// PART 19: SYMBOLS IN MAP
// ============================================
const map = new Map();
const key = Symbol('key');
map.set(key, 'value');
console.log(map.get(key));
// [ 'value' ]
// ============================================
// PART 20: FULL SCRIPT
// ============================================
const sym1_52 = Symbol();
const sym2_52 = Symbol('description');
const sym3_52 = Symbol('description');
console.log(sym1_52);
console.log(sym2_52);
console.log(sym2_52 === sym3_52);
const obj52 = {
[sym1_52]: 'value1',
[sym2_52]: 'value2',
regular: 'regular'
};
console.log(obj52[sym1_52]);
console.log(obj52[sym2_52]);
console.log(Object.keys(obj52));
const globalSym1_52 = Symbol.for('app.id');
const globalSym2_52 = Symbol.for('app.id');
console.log(globalSym1_52 === globalSym2_52);
console.log(Symbol.keyFor(globalSym1_52));
const iterable52 = {
[Symbol.iterator]() {
let i = 0;
return {
next: () => i < 3 ? { value: i++, done: false } : { done: true }
};
}
};
console.log([...iterable52]);
console.log(Symbol.iterator);
console.log(Symbol.asyncIterator);
console.log(Symbol.hasInstance);
console.log(Symbol.toPrimitive);
console.log(Symbol.toStringTag);
Quick Reference
Creating Symbols
| Syntax | Result |
|---|---|
Symbol() | Unique, no description |
Symbol('desc') | Unique, with description |
Symbol.for('key') | Shared from registry |
Symbol.keyFor(sym) | Key or undefined |
Symbol Properties
| Property | Meaning |
|---|---|
sym.description | Description string |
sym.toString() | Symbol(desc) |
Symbols as Keys
| Operation | Behavior |
|---|---|
obj[sym] | Access |
obj[sym] = v | Assign |
Object.keys(obj) | ❌ excludes symbols |
Object.getOwnPropertySymbols(obj) | Returns symbols only |
Reflect.ownKeys(obj) | Strings + symbols |
JSON.stringify(obj) | ❌ excludes symbols |
for...in | ❌ excludes symbols |
{ ...obj } | ✅ copies enumerable symbols |
Well-Known Symbols
| Symbol | Purpose |
|---|---|
Symbol.iterator | Iteration |
Symbol.asyncIterator | Async iteration |
Symbol.hasInstance | instanceof |
Symbol.toPrimitive | Type coercion |
Symbol.toStringTag | toString tag |
Symbol.isConcatSpreadable | concat behavior |
Symbol.species | Derived constructor |
Symbol.match | String.match |
Symbol.replace | String.replace |
Symbol.search | String.search |
Symbol.split | String.split |
Symbol.unscopables | with exclusions |
Global vs Local
| Type | Created with | Same across modules |
|---|---|---|
| Local | Symbol() | ❌ |
| Global | Symbol.for('key') | ✅ |
Symbol vs Private Field
| Feature | Symbol | #private |
|---|---|---|
| Hidden | ✅ (convention) | ✅ (truly) |
| Reflective | Via getOwnPropertySymbols | No |
| Shared | Via Symbol.for | No |
| Support | ES2015 | ES2022 |
Best Practices
✅ Do This:
// Use Symbols for unique keys
const ID = Symbol('id'); // ✅
// Use descriptive strings
const KEY = Symbol('user.id'); // ✅
// Use Symbol.for for shared keys
const SHARED = Symbol.for('app.shared'); // ✅
// Use Symbol.iterator for iterables
class Range {
[Symbol.iterator]() { ... } // ✅
}
// Access Symbol keys with brackets
obj[sym] // ✅
// Enumerate Symbols explicitly
Object.getOwnPropertySymbols(obj); // ✅
// Use Symbol.hasInstance for custom instanceof
static [Symbol.hasInstance](x) { ... } // ✅
// Use Symbol.toPrimitive for coercion
[Symbol.toPrimitive](hint) { ... } // ✅
❌ Don’t Do This:
// Don't use dot notation for Symbol keys
obj.sym // ❌ undefined
obj[sym] // ✅
// Don't expect Object.keys to include Symbols
Object.keys(obj); // ❌ misses symbols
// Don't JSON.stringify symbols
JSON.stringify(obj); // ❌ skipped
// Don't use Symbol() with the same description expecting equality
Symbol('x') === Symbol('x'); // ❌ false
// Don't confuse Symbol.for with Symbol
Symbol.for('x') === Symbol('x'); // ❌ false
// Don't use Symbol.new — it's not a constructor
new Symbol(); // ❌ TypeError
// Don't rely on Symbols for real privacy
obj[sym]; // ⚠️ accessible if known
// Don't overuse Symbols
const NAME = Symbol('name'); // ⚠️ if collision is impossible
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
new Symbol() | TypeError | Symbol() |
| Dot notation | undefined | obj[sym] |
Object.keys | Skips Symbols | getOwnPropertySymbols |
JSON.stringify | Skips Symbols | Serialize manually |
| Description equality | Symbols still unique | Use Symbol.for to share |
| Expecting privacy | Symbol accessible | Use #private |
| Forgetting brackets | Wrong key | [sym]: value |
Symbol.for vs Symbol | Different behavior | Know which you need |
Real-World Examples
1. Create Symbol
const sym = Symbol('id');
console.log(typeof sym);
// [ 'symbol' ]
2. Symbols Are Unique
console.log(Symbol('a') === Symbol('a'));
// [ false ]
3. Symbol as Key
const sym = Symbol('id');
const obj = { [sym]: 42, name: 'Alice' };
console.log(obj[sym]);
// [ 42 ]
4. Hidden from Enumeration
const sym = Symbol('id');
const obj = { [sym]: 42, name: 'Alice' };
console.log(Object.keys(obj));
// [ [ 'name' ] ]
console.log(Object.getOwnPropertySymbols(obj).length);
// [ 1 ]
5. JSON Omits Symbols
const sym = Symbol('id');
const obj = { [sym]: 42, name: 'Alice' };
console.log(JSON.stringify(obj));
// [ '{"name":"Alice"}' ]
6. Global Symbol
const a = Symbol.for('shared');
const b = Symbol.for('shared');
console.log(a === b);
// [ true ]
7. Symbol.keyFor
console.log(Symbol.keyFor(Symbol.for('x')));
// [ 'x' ]
console.log(Symbol.keyFor(Symbol('x')));
// [ undefined ]
8. Description
console.log(Symbol('hello').description);
// [ 'hello' ]
console.log(Symbol('hello').toString());
// [ 'Symbol(hello)' ]
9. Symbol.iterator
const it = {
[Symbol.iterator]() {
let i = 0;
return {
next: () => i < 3 ? { value: i++, done: false } : { done: true }
};
}
};
console.log([...it]);
// [ [ 0, 1, 2 ] ]
10. Symbol.hasInstance
class Even {
static [Symbol.hasInstance](v) {
return typeof v === 'number' && v % 2 === 0;
}
}
console.log(2 instanceof Even);
// [ true ]
11. Symbol.toPrimitive
const obj = {
[Symbol.toPrimitive](hint) {
return hint === 'number' ? 42 : 'hello';
}
};
console.log(+obj);
// [ 42 ]
console.log(`${obj}`);
// [ 'hello' ]
12. Symbol.toStringTag
class Matrix {
get [Symbol.toStringTag]() { return 'Matrix'; }
}
console.log(Object.prototype.toString.call(new Matrix()));
// [ '[object Matrix]' ]
13. Reflect.ownKeys
const sym = Symbol('x');
const obj = { [sym]: 1, name: 'Alice' };
console.log(Reflect.ownKeys(obj).length);
// [ 2 ]
14. Spread Copies Symbols
const sym = Symbol('x');
const src = { [sym]: 1, name: 'Alice' };
const copy = { ...src };
console.log(copy[sym]);
// [ 1 ]
15. Enum Constants
const Colors = {
RED: Symbol('red'),
GREEN: Symbol('green')
};
console.log(typeof Colors.RED);
// [ 'symbol' ]
16. Symbols in Map
const map = new Map();
const k = Symbol('k');
map.set(k, 'value');
console.log(map.get(k));
// [ 'value' ]
17. Hidden Metadata
const META = Symbol('meta');
class User {
constructor(name) {
this.name = name;
this[META] = { created: Date.now() };
}
}
const u = new User('Alice');
console.log(Object.keys(u));
// [ [ 'name' ] ]
console.log(typeof u[META].created);
// [ 'number' ]
18. Iterating Symbol Keys
const s1 = Symbol('a');
const s2 = Symbol('b');
const obj = { [s1]: 1, [s2]: 2 };
for (const sym of Object.getOwnPropertySymbols(obj)) {
console.log(sym.description, obj[sym]);
}
// [ 'a' 1 ]
// [ 'b' 2 ]
19. Checking for Symbol
function isSymbol(x) {
return typeof x === 'symbol';
}
console.log(isSymbol(Symbol()));
// [ true ]
console.log(isSymbol('x'));
// [ false ]
20. Full Script
const sym1_52 = Symbol();
const sym2_52 = Symbol('description');
const sym3_52 = Symbol('description');
console.log(sym1_52);
console.log(sym2_52);
console.log(sym2_52 === sym3_52);
const obj52 = {
[sym1_52]: 'value1',
[sym2_52]: 'value2',
regular: 'regular'
};
console.log(obj52[sym1_52]);
console.log(obj52[sym2_52]);
console.log(Object.keys(obj52));
const globalSym1_52 = Symbol.for('app.id');
const globalSym2_52 = Symbol.for('app.id');
console.log(globalSym1_52 === globalSym2_52);
console.log(Symbol.keyFor(globalSym1_52));
const iterable52 = {
[Symbol.iterator]() {
let i = 0;
return {
next: () => i < 3 ? { value: i++, done: false } : { done: true }
};
}
};
console.log([...iterable52]);
console.log(Symbol.iterator);
console.log(Symbol.asyncIterator);
console.log(Symbol.hasInstance);
console.log(Symbol.toPrimitive);
console.log(Symbol.toStringTag);
Visual: Symbol Uniqueness
┌──────────────────────────────────────────────┐
│ Symbol('id') !== Symbol('id') │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Symbol('id') │ │ Symbol('id') │ │
│ │ unique #1 │ │ unique #2 │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ Same description, different values │
│ │
│ Symbol.for('id') === Symbol.for('id') │
│ │
│ ┌──────────────┐ │
│ │ Symbol.for │ shared from registry │
│ │ ('id') │ ────────────────► │
│ └──────────────┘ │
│ │
└──────────────────────────────────────────────┘
Visual: Hidden vs Enumerable
┌──────────────────────────────────────────────┐
│ const sym = Symbol('id'); │
│ const obj = { [sym]: 42, name: 'Alice' }; │
│ │
│ Object.keys(obj) │
│ → ['name'] │
│ │
│ Object.values(obj) │
│ → ['Alice'] │
│ │
│ JSON.stringify(obj) │
│ → '{"name":"Alice"}' │
│ │
│ for (const k in obj) │
│ → 'name' only │
│ │
│ Object.getOwnPropertySymbols(obj) │
│ → [Symbol(id)] │
│ │
│ Reflect.ownKeys(obj) │
│ → ['name', Symbol(id)] │
│ │
└──────────────────────────────────────────────┘
Visual: Well-Known Symbol Protocols
┌──────────────────────────────────────────────┐
│ Your Object │
│ │
│ class Range { │
│ [Symbol.iterator]() { ... } ← for...of │
│ [Symbol.toPrimitive]() { ... } ← +, `${}` │
│ static [Symbol.hasInstance]() ← instanceof│
│ get [Symbol.toStringTag]() ← toString │
│ } │
│ │
│ These hooks integrate with the language │
│ │
└──────────────────────────────────────────────┘
Summary
| Concept | Syntax | Example |
|---|---|---|
| Create | Symbol() | Unique symbol |
| Description | Symbol('id') | For debugging |
| Global | Symbol.for('key') | Shared |
| Reverse | Symbol.keyFor(sym) | Key or undefined |
| Type | typeof sym | 'symbol' |
| Object key | obj[sym] | Bracket notation |
| Hidden | Object.keys(obj) | Skips symbols |
| Get symbols | Object.getOwnPropertySymbols(obj) | Array of symbols |
| All keys | Reflect.ownKeys(obj) | Strings + symbols |
| Copy | { ...obj } | Copies symbols |
| JSON | JSON.stringify(obj) | Skips symbols |
| Iterator | [Symbol.iterator] | for...of |
| Async iterator | [Symbol.asyncIterator] | for await...of |
| hasInstance | static [Symbol.hasInstance] | instanceof |
| toPrimitive | [Symbol.toPrimitive] | +, ${} |
| toStringTag | get [Symbol.toStringTag] | toString |
| Description | sym.description | 'id' |
Key takeaways:
Symbolis a primitive — always unique, even with the same description- Use
Symbol('desc')for local keys,Symbol.for('key')for shared ones - Symbols as object keys are hidden from
Object.keys,for...in, andJSON.stringify - Access Symbol keys with bracket notation — dot notation fails
- Enumerate Symbols with
Object.getOwnPropertySymbolsorReflect.ownKeys - Spread (
{...obj}) copies enumerable Symbol properties - Well-known Symbols —
Symbol.iterator,Symbol.hasInstance,Symbol.toPrimitive,Symbol.toStringTag— hook into language protocols - Implement
[Symbol.iterator]to make your object iterable withfor...of Symbol.foruses a global registry — different modules get the same Symbol- Symbols are not truly private — use
#privateclass fields for real encapsulation - Use Symbols for metadata, collision-proof keys, enum constants, and protocol hooks
Remember: Symbols are unique, hidden, protocol-aware keys. Reach for them when you need a property that won’t collide with anything else, or when you’re implementing a language protocol like iteration or type coercion. Use Symbol.for when different modules need the same key. Remember that Symbols are invisible to most enumeration — but accessible if someone knows the Symbol. Master Symbols, and you control the hidden layer of every object.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!