Symbols
Learn how JavaScript symbols create unique property keys that never collide, how the global symbol registry works, and why some properties stay out of ordinary object lists.
- 01Create unique keysUse
Symbol()and explain why matching descriptions do not make symbols equal. - 02Share when you mean toReach for
Symbol.forandSymbol.keyForwhen two pieces of code need the same symbol. - 03Inspect hidden propertiesPredict which object tools skip symbol keys and which tools reveal them.
Symbols are one-of-a-kind property keys
A symbol is a primitive value made to be unique. You mostly use symbols as object property keys when you want a key that cannot accidentally collide with somebody else’s key.
You met symbol in the Data types lesson: typeof Symbol("id") is "symbol". In Object basics, you also saw computed property names: { [key]: value }. Put those ideas together and a symbol can become a special object key.
Imagine a key cut for one exact lock. You can tie a tag labeled “id” to it, but the tag is not the key. If a locksmith cuts a second key and puts the same tag on it, you still have two different keys.
- In real life: A locksmith cuts one unique key
- In JavaScript:
Symbol()creates one unique value - In real life: A tag that says “id” on the key ring
- In JavaScript: The symbol’s
description - In real life: Two tags can both say “id”
- In JavaScript:
Symbol("id") !== Symbol("id") - In real life: A hidden drawer opens only if you kept the key
- In JavaScript:
object[symbolKey]reads a symbol property
Where the analogy stops: A real key can be copied by a locksmith. A local JavaScript symbol cannot be recreated from its description. But if code already has the symbol, it can read the property; symbols are not security locks.
This lesson covers Symbol(), descriptions, the shared registry, symbol-keyed properties that ordinary object lists skip, and a first peek at well-known symbols like Symbol.iterator.
Symbol() and description
STEP THROUGHCall Symbol(description) to create a symbol. The description is optional. It is useful in debugging output, but it does not participate in equality.
Change the description below and step through. Even when both calls use the same text, JavaScript creates two different symbols.
Pick a description, predict the comparisons, then step through real symbol values.
script
const sameTag = Symbol("id");console.log(id === sameTag);console.log(id === id);console.log(typeof id);console.log(id.description);console.log(String(id));Symbol("id") reads nicely in logs, but the identity is the symbol value itself. Store it in a variable if you want to use the same key again.
Descriptions are not automatic strings
STEP THROUGHSymbols intentionally resist implicit string conversion. That sounds fussy until it saves you from accidentally treating a unique key like ordinary text.
The safe conversions are explicit: String(id) or id.toString(). Concatenating with + throws a TypeError in JavaScript engines such as V8.
Symbols need explicit string conversion. Step through the safe lines, then the failing one.
script
console.log(String(id));console.log(id.toString());console.log("Key: " + id);String(id) gives something like Symbol(id). That is a display string, not a way to rebuild the original symbol.
The global registry: Symbol.for and Symbol.keyFor
STEP THROUGHSometimes two separate files really do need the same symbol. The global symbol registry is a shared key cabinet. Ask for Symbol.for("app.id"), and everyone using that exact key gets the same symbol back.
Local symbols are keys you cut for yourself. Registry symbols are keys from a cabinet in the lobby: if two teams ask for app.id, the clerk hands both teams the same key.
- In real life: A building lobby key cabinet
- In JavaScript: The global symbol registry
- In real life: A label like
app.id - In JavaScript: The registry key string
- In real life: Everyone asks the desk for the same labeled key
- In JavaScript: Every
Symbol.for("app.id")gets the same symbol - In real life: Looking up which hook a key came from
- In JavaScript:
Symbol.keyFor(symbol)
Where the analogy stops: The registry is shared across a JavaScript realm, so key names should be specific. Use Symbol.for when sharing is the point, not for private metadata.
Compare a local symbol with a shared registry symbol. Same words, different rules.
script
const sharedA = Symbol.for("app.id");const sharedB = Symbol.for("app.id");console.log(local === Symbol("app.id"));console.log(sharedA === sharedB);console.log(Symbol.keyFor(sharedA));console.log(Symbol.keyFor(local));| Question | Symbol(description) | Symbol.for(key) |
|---|---|---|
| Does it create a new symbol each call? | Yes. Every call returns a fresh symbol. | Only when the registry has no symbol for that key yet. |
| Do matching words make values equal? | No. The description is just a debugging label. | Yes, if the words are the same registry key. |
Can Symbol.keyFor find it? | No. It returns undefined for local symbols. | Yes. It returns the registry key string. |
| Best use | Private-ish object keys and collision-free metadata. | Shared protocols between modules or packages. |
A first look at well-known symbols
PREVIEWJavaScript also has built-in symbol keys called well-known symbols. They are hooks where objects can opt into language behavior. You do not need to master them today; just recognize the shape.
typeof [][Symbol.iterator];typeof "hi"[Symbol.iterator];typeof ({})[Symbol.iterator];for (const value of {}) { console.log(value);}Object.prototype.toString.call([]);const tagged = { [Symbol.toStringTag]: "LibraryCard",};Object.prototype.toString.call(tagged);typeof [][Symbol.iterator] → function
A for...of loop asks for the method stored at Symbol.iterator. Arrays have one.
A for...of loop asks for the method stored at Symbol.iterator. Arrays have one.
Symbol.iteratoris whyfor...ofworks on arrays and strings. Plain objects do not have it by default.Symbol.toStringTagcustomizes labels such as[object Array].Symbol.toPrimitivecontrols object-to-primitive conversion. That is the focus of the next lesson, so this is only a teaser.
Where you’ll use symbols
The everyday use case is metadata on objects you do not own. Imagine a small library that receives user objects from an app. The app might already use keys like id, status, or source. A symbol key avoids collisions.
const processed = Symbol("processed");
export function markProcessed(record) {
record[processed] = true;
return record;
}
export function wasProcessed(record) {
return record[processed] === true;
}The important trade-off: symbol metadata is polite, not secret. It avoids accidental name clashes and stays out of JSON, but inspection tools can still reveal it.
Common misconceptions
“The description makes symbols equal.”
No. Symbol("id") === Symbol("id") is false. The description is a debugging label.
“Symbol properties are private.”
They are hidden from ordinary lists, not private. Anyone with the symbol can read them, and Object.getOwnPropertySymbols can discover them.
“Use Symbol.for for everything.”
Use the registry when sharing is intentional. For local metadata, Symbol() avoids global key collisions.
“JSON lost my data.”
JSON never includes symbol-keyed properties. Keep important data on string keys, or convert it intentionally before serializing.
“Well-known symbols are just constants.”
They are symbol keys that JavaScript itself checks. The function stored at Symbol.iterator changes how an object works with for...of.
Practice: symbols
5 EXERCISESPredict the three console lines before checking the answer.
const first = Symbol("id");
const second = Symbol("id");
console.log(first === second);
console.log(first === first);
console.log(typeof first);The first comparison is false because the two calls made two different symbols. The second is true because it compares the variable with itself. typeof first is symbol.
What does the program print after adding symbol-keyed metadata?
const metadata = Symbol("metadata");
const user = { name: "Ada" };
user[metadata] = { role: "admin" };
console.log(JSON.stringify(user));JSON.stringify skips the symbol-keyed metadata property, so the printed JSON contains only the string key: {"name":"Ada"}.
Rewrite the second line so it prints a label instead of throwing.
const id = Symbol("id");
console.log("Key: " + id);const id = Symbol("id");
console.log("Key: " + String(id));String(id) is explicit, so concatenation receives ordinary text. The fixed program prints Key: Symbol(id).
Imagine the two lines are in separate modules. Do they share the same key or create separate keys?
const moduleAKey = Symbol.for("course.userId");
const moduleBKey = Symbol.for("course.userId");
console.log(moduleAKey === moduleBKey);Both calls use Symbol.for("course.userId"), so both variables receive the same symbol from the global registry. The comparison prints true.
This is a preview of the iteration protocols. Predict what the custom iterable prints.
const colors = {
values: ["red", "blue"],
[Symbol.iterator]() {
return this.values[Symbol.iterator]();
}
};
console.log([...colors].join(","));The object opts into iteration by defining [Symbol.iterator]. Spreading it produces red and blue, so the console prints red,blue.
Quiz: check your understanding
7 QUESTIONSQuestion 1 of 5What does comparing two fresh symbols print?
Read the code, then predictconsole.log(Symbol("id") === Symbol("id"));Choose an answer to see the explanation.
Question 2 of 5What is
id.descriptionforconst id = Symbol("user")?Read the code, then predictconst id = Symbol("user"); console.log(id.description);Choose an answer to see the explanation.
Question 3 of 5Which expression gives two modules the same symbol value?
Choose an answer to see the explanation.
Question 4 of 5What does
Symbol.keyFor(Symbol("local"))return?Read the code, then predictconsole.log(Symbol.keyFor(Symbol("local")));Choose an answer to see the explanation.
Question 5 of 5Which tool lists symbol keys and string keys together?
Choose an answer to see the explanation.
Question 1 of 1What happens here?
Read the code, then predictconst id = Symbol("id"); console.log("Key: " + id);Choose an answer to see the explanation.
Question 1 of 1What does the array iterator check print?
Read the code, then predictconsole.log(typeof [][Symbol.iterator]);Choose an answer to see the explanation.
Key takeaways
Symbol()creates a unique primitive value every time.- The
descriptionhelps humans read logs; it does not make symbols equal. Symbol.forandSymbol.keyForwork with a shared registry for intentionally shared keys.- Symbol-keyed object properties are skipped by
Object.keys,for...in, andJSON.stringify, but symbol-aware tools can find them. - Well-known symbols such as
Symbol.iteratorare hooks JavaScript checks for special behavior.
Remember the one-liner.
A symbol is a unique primitive value, most often used as an object property key that cannot accidentally collide with a string key or another symbol.
Up next: Object to primitive conversion — including the deeper version of Symbol.toPrimitive teased here.