ESC

Type to search the knowledge base.

Symbols in JavaScript

Unique property keys with Symbol — privacy lite, well-known symbols (iterator, toStringTag), Symbol.for, and enumeration rules.

intermediate3 min read
  • javascript
  • symbols-in

A Symbol is a primitive unique value, often used as an object property key that won’t collide with string keys from other code.

const id = Symbol('id');
const id2 = Symbol('id');
id === id2; // false — description is only a label

const user = {
  name: 'Ada',
  [id]: 123,
};
user[id]; // 123

Hidden from everyday enumeration

Object.keys(user); // ['name']
for (const k in user) { /* name only */ }
JSON.stringify(user); // '{"name":"Ada"}' — symbols skipped

Object.getOwnPropertySymbols(user); // [Symbol(id)]
Reflect.ownKeys(user); // string keys + symbols

This is not real privacy (reflection finds them). For hard privacy use # fields or WeakMaps.

Global registry: Symbol.for

const a = Symbol.for('app.session');
const b = Symbol.for('app.session');
a === b; // true — shared across realm’s registry

Symbol.keyFor(a); // 'app.session'
Symbol.keyFor(Symbol('x')); // undefined — not in registry

Use Symbol.for when multiple modules/realms need the same well-known key string. Use Symbol() when uniqueness is the point.

Well-known symbols (protocol hooks)

Symbol Role
Symbol.iterator default iterator → for...of, spread
Symbol.asyncIterator for await...of
Symbol.toStringTag Object.prototype.toString tag
Symbol.toPrimitive conversion to primitive
Symbol.hasInstance customize instanceof
const range = {
  from: 1,
  to: 3,
  [Symbol.iterator]() {
    let n = this.from;
    return {
      next: () =>
        n <= this.to
          ? { value: n++, done: false }
          : { done: true },
    };
  },
};
[...range]; // [1,2,3]
class AuthError extends Error {
  get [Symbol.toStringTag]() {
    return 'AuthError';
  }
}
Object.prototype.toString.call(new AuthError()); // [object AuthError]

Property definition

const secret = Symbol('secret');
const o = {};
Object.defineProperty(o, secret, {
  value: 42,
  enumerable: false,
});

String keys and symbol keys coexist; setting o[secret] doesn’t overwrite o.secret string key.

Cross-realm note

Symbols from different iframes are different values even with the same description — except Symbol.for shared within the agent’s registry rules. Don’t expect postMessage to preserve your symbol identity as a key protocol without strings.

Interview answer (out loud)

“Symbols are unique primitives used as non-colliding property keys. They’re skipped by Object.keys and JSON. Symbol.for reuses a global registry key. Well-known symbols hook language protocols like iteration. They’re meta-programming tools, not a security boundary.”

Library extension pattern

const secret = Symbol('my-lib.internal');

export function attach(obj, data) {
  obj[secret] = data; // won’t clash with user string keys
}

export function read(obj) {
  return obj[secret];
}

Safer than obj.__myLib strings. Still visible via getOwnPropertySymbols — don’t store credentials this way.

Enums and constants

export const Color = {
  Red: Symbol('red'),
  Blue: Symbol('blue'),
};
// switch with === works; JSON won’t round-trip — use strings for wire formats

Symbols make poor API response enums; good for in-memory exclusive modes.

mix with proxies

ownKeys traps must report symbol keys correctly if you want getOwnPropertySymbols to stay consistent — advanced meta-programming territory.

Further reading

Related guides