Symbols in JavaScript
Unique property keys with Symbol — privacy lite, well-known symbols (iterator, toStringTag), Symbol.for, and enumeration rules.
- 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
- Iterators and the Iterable Protocol
- Object.create and the prototype chain
- Private Class Fields
- Proxy and Reflect
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.