|

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 caseWhy Symbols
Hidden propertiesNot enumerable by default
Unique keysNo collision with strings
MetadataInvisible to normal code
Protocol hooksSymbol.iterator, etc.
Registry keysSymbol.for shares across modules

Symbol vs String keys:

FeatureString keySymbol key
UniquenessCollisions possibleAlways unique
EnumerationObject.keys includesHidden
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.

SymbolPurpose
Symbol.iteratorIteration protocol
Symbol.asyncIteratorAsync iteration
Symbol.hasInstanceinstanceof behavior
Symbol.toPrimitiveType coercion
Symbol.toStringTagCustom toString
Symbol.isConcatSpreadableSpreadable in concat
Symbol.speciesDerived constructor
Symbol.matchString.match behavior
Symbol.replaceString.replace behavior
Symbol.searchString.search behavior
Symbol.splitString.split behavior
Symbol.unscopablesExcluded 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:

SymbolWhere used
Symbol.iteratorfor...of, spread, destructuring
Symbol.asyncIteratorfor await...of
Symbol.hasInstanceinstanceof
Symbol.toPrimitive+, ${}, ==
Symbol.toStringTagObject.prototype.toString
Symbol.isConcatSpreadableArray.prototype.concat
Symbol.speciesDerived constructors
Symbol.matchString.prototype.match
Symbol.replaceString.prototype.replace
Symbol.searchString.prototype.search
Symbol.splitString.prototype.split
Symbol.unscopableswith 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 casePattern
Hidden dataSymbol key + non-enumerable
Unique IDsSymbol()
Shared keysSymbol.for
Custom iteration[Symbol.iterator]
Type coercion[Symbol.toPrimitive]
Custom instanceof[Symbol.hasInstance]
Framework metadataSymbol + decorators
Enum valuesObject of Symbols

Symbols vs private fields:

FeatureSymbol#private
HidingConventionTruly private
ReflectiongetOwnPropertySymbolsNone
SharedVia Symbol.forNo
InheritanceSymbols inheritPrivate fields don’t
SupportES2015ES2022
UseMetadata, protocolsTrue 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

SyntaxResult
Symbol()Unique, no description
Symbol('desc')Unique, with description
Symbol.for('key')Shared from registry
Symbol.keyFor(sym)Key or undefined

Symbol Properties

PropertyMeaning
sym.descriptionDescription string
sym.toString()Symbol(desc)

Symbols as Keys

OperationBehavior
obj[sym]Access
obj[sym] = vAssign
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

SymbolPurpose
Symbol.iteratorIteration
Symbol.asyncIteratorAsync iteration
Symbol.hasInstanceinstanceof
Symbol.toPrimitiveType coercion
Symbol.toStringTagtoString tag
Symbol.isConcatSpreadableconcat behavior
Symbol.speciesDerived constructor
Symbol.matchString.match
Symbol.replaceString.replace
Symbol.searchString.search
Symbol.splitString.split
Symbol.unscopableswith exclusions

Global vs Local

TypeCreated withSame across modules
LocalSymbol()
GlobalSymbol.for('key')

Symbol vs Private Field

FeatureSymbol#private
Hidden✅ (convention)✅ (truly)
ReflectiveVia getOwnPropertySymbolsNo
SharedVia Symbol.forNo
SupportES2015ES2022

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

PitfallProblemSolution
new Symbol()TypeErrorSymbol()
Dot notationundefinedobj[sym]
Object.keysSkips SymbolsgetOwnPropertySymbols
JSON.stringifySkips SymbolsSerialize manually
Description equalitySymbols still uniqueUse Symbol.for to share
Expecting privacySymbol accessibleUse #private
Forgetting bracketsWrong key[sym]: value
Symbol.for vs SymbolDifferent behaviorKnow 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

ConceptSyntaxExample
CreateSymbol()Unique symbol
DescriptionSymbol('id')For debugging
GlobalSymbol.for('key')Shared
ReverseSymbol.keyFor(sym)Key or undefined
Typetypeof sym'symbol'
Object keyobj[sym]Bracket notation
HiddenObject.keys(obj)Skips symbols
Get symbolsObject.getOwnPropertySymbols(obj)Array of symbols
All keysReflect.ownKeys(obj)Strings + symbols
Copy{ ...obj }Copies symbols
JSONJSON.stringify(obj)Skips symbols
Iterator[Symbol.iterator]for...of
Async iterator[Symbol.asyncIterator]for await...of
hasInstancestatic [Symbol.hasInstance]instanceof
toPrimitive[Symbol.toPrimitive]+, ${}
toStringTagget [Symbol.toStringTag]toString
Descriptionsym.description'id'

Key takeaways:

  • Symbol is 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, and JSON.stringify
  • Access Symbol keys with bracket notation — dot notation fails
  • Enumerate Symbols with Object.getOwnPropertySymbols or Reflect.ownKeys
  • Spread ({...obj}) copies enumerable Symbol properties
  • Well-known SymbolsSymbol.iterator, Symbol.hasInstance, Symbol.toPrimitive, Symbol.toStringTag — hook into language protocols
  • Implement [Symbol.iterator] to make your object iterable with for...of
  • Symbol.for uses a global registry — different modules get the same Symbol
  • Symbols are not truly private — use #private class 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!