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 keysMake symbols with
Symbol(). Explain why two symbols with the same description are not equal. - 02Share when you mean toUse
Symbol.forandSymbol.keyForwhen two pieces of code need the same symbol. - 03Find symbol keysSay which object tools skip symbol keys and which tools show them.
Symbols are one-of-a-kind property keys
A symbol is a value that is always unique. No other value is ever equal to it. A symbol is a primitive value, like a number or a string.
The main job of a symbol is to be a property key. A property key is the name you use to store and read a value on an object, like name in user.name. Because every symbol is unique, a symbol key can never be the same as someone else's key by accident.
You met symbol in the Data types lesson: typeof Symbol("id") is "symbol". In Object basics, you saw computed property names, like { [key]: value }. The square brackets mean "use the value of key as the property name". A symbol can go inside those brackets. That is how a symbol becomes a property key.
A locksmith cuts a key for one exact lock. You can tie a tag that says “id” to the key. The tag is not the key. If the locksmith cuts a second key and ties the same tag to 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 on the key that says “id”
- In JavaScript: The symbol’s
description - In real life: Two keys can both have a tag that says “id”
- In JavaScript:
Symbol("id") !== Symbol("id") - In real life: A drawer opens only with that exact key
- In JavaScript:
object[symbolKey]reads a symbol property
Here is a first example. A variable keeps the symbol. Square brackets use that exact symbol to write and read a property.
const userKey = Symbol("userId");
const user = { name: "Asha" };
user[userKey] = 42;
console.log(user[userKey]);
console.log(user.userId);- Line 1 makes a symbol and saves it in
userKey. The text"userId"is the symbol's description: a label for people to read. - Line 2 makes a
userobject with one string key,name. - Line 3 stores
42onuser, with the symbol as the key. - Line 4 reads with the same symbol, so it prints
42. - Line 5 prints
undefined.user.userIdlooks for a key that is the text"userId". That text is not the symbol, even though the symbol's description saysuserId.
This lesson covers:
Symbol()and descriptions.- How to turn a symbol into text.
- The shared list of symbols, called the registry, with
Symbol.for. - Symbol keys that ordinary object lists skip.
- A first look at built-in symbols like
Symbol.iterator.
Symbol() and description
STEP THROUGHCall Symbol() to make a new symbol. You can put a description inside the brackets, like Symbol("id"). The description is optional. It shows up when you print the symbol, so it helps when you look for bugs. It does not decide whether two symbols are equal.
Pick a description below and step through the code. Lines 1 and 2 use the same text. JavaScript still makes two different symbols.
Pick a description, predict each line, then step through.
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));id. They are still two different symbols, so id === sameTag is false.Symbol("id") is easy to read in a log. But what counts is the symbol value itself. To use the same key again, save the symbol in a variable and use that variable.
You can leave the description out. Then a log shows less, but the symbol is still unique.
const note = Symbol();
console.log(note.description === undefined);
console.log(String(note));This prints true, then Symbol(). Line 2 checks that the description is missing, which means it is undefined. Line 3 turns the symbol into text. With no description, the text is just Symbol().
A symbol doesn't turn into text by itself
STEP THROUGHMany values turn into text on their own. For example, "Age: " + 5 gives "Age: 5". Symbols refuse to do this. That is on purpose: it stops you from mixing up a unique key with ordinary text by mistake.
To get text from a symbol, ask for it directly. There are two ways:
String(id)id.toString()
Joining a symbol to text with + throws a TypeError. A TypeError is the error JavaScript throws when a value is the wrong type for what you asked.
A symbol turns into text only when you ask directly. Step through the two safe lines, then the line that throws.
script
console.log(String(id));console.log(id.toString());console.log("Key: " + id);String(id), and you get "Symbol(id)". Let + do it for you, and JavaScript throws a TypeError.String(id) gives text like Symbol(id). You can show that text to people. You can't turn it back into the original symbol.
The global registry: Symbol.for and Symbol.keyFor
STEP THROUGHSometimes two separate files really do need the same symbol. For that, JavaScript keeps one shared list of symbols, called the global symbol registry. Each symbol in it is saved under a text key, such as "app.id".
Call Symbol.for("app.id"), and you get the symbol saved under that key. If there isn't one yet, JavaScript makes it and saves it. Everyone who uses that exact key gets the same symbol.
The symbols you make with Symbol() are keys you cut for yourself. Registry symbols are keys from a cabinet in the building lobby. Each key hangs on a hook with a label. If two teams ask the desk for the app.id key, the desk gives both teams the same key.
- In real life: A key cabinet in the building lobby
- In JavaScript: The global symbol registry
- In real life: A hook with the label
app.id - In JavaScript: The registry key, a piece of text
- In real life: Everyone who asks for the
app.idkey gets the same key - In JavaScript: Every
Symbol.for("app.id")gets the same symbol - In real life: Reading the label on the hook a key came from
- In JavaScript:
Symbol.keyFor(symbol)
Step through the code below. Line 1 makes a local symbol: a symbol from Symbol(), which is not in the registry. Lines 2 and 3 ask the registry. Symbol.keyFor, on lines 6 and 7, does the opposite lookup. Give it a registry symbol, and it returns the key that symbol is saved under.
Compare a local symbol with two registry symbols. Same text, 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));Symbol.for("app.id") calls look in the registry under the key app.id. They get back the same symbol, so sharedA === sharedB is true.Registry keys are always text. If you pass something else, JavaScript turns it into text first. So the number 7 and the string "7" find the same registry entry. In real code, write clear text keys, so the next reader doesn't need to know this rule.
const first = Symbol.for(7);
const second = Symbol.for("7");
console.log(first === second);
console.log(Symbol.keyFor(first));The output is true, then 7. Line 1 asks for the key 7, which becomes the text "7". Line 2 asks for "7", so both lines get one shared symbol. Line 4 prints the key that symbol is saved under.
The table compares the two ways to make a symbol. Read each row as a question, then the answer for each way.
| Question | Symbol(description) | Symbol.for(key) |
|---|---|---|
| Does each call make a new symbol? | Yes. Every call returns a new symbol. | Only when the registry has no symbol saved under that key yet. |
| Does the same text give the same symbol? | No. The description is only a label. | Yes. The same key always gives back the same symbol. |
Can Symbol.keyFor find it? | No. It returns undefined for a local symbol. | Yes. It returns the key the symbol is saved under. |
| When should I use it? | For your own object keys that must not clash with other keys. | When code in different files must share one symbol on purpose. |
A first look at well-known symbols
PREVIEWJavaScript also comes with built-in symbols, called well-known symbols. You find them on Symbol, like Symbol.iterator. JavaScript itself looks for these keys on objects. If an object has a property under one of these keys, JavaScript uses it to change how the object behaves.
You don't need to learn them all today. Just learn to recognize them.
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 function stored under Symbol.iterator. Arrays have one.
A for...of loop asks for the function stored under Symbol.iterator. Arrays have one.
Symbol.iterator: afor...ofloop looks for a function under this key. Arrays and strings have one, sofor...ofworks on them. Plain objects don't have one.Symbol.toStringTag: sets the name inside labels like[object Array]. These labels come fromObject.prototype.toString.Symbol.toPrimitive: decides how an object turns into a primitive value, like a number or a string. The next lesson covers it, so this is only a first look.
Where you’ll use symbols
The most common use is adding metadata to objects you don't own. Imagine a small library: code written by someone else that your app uses. The library receives user objects from your app. Your app might already use keys like id, status, or source. If the library adds its own notes under a symbol key, it can never overwrite one of your app's keys.
const processed = Symbol("processed");
export function markProcessed(record) {
record[processed] = true;
return record;
}
export function wasProcessed(record) {
return record[processed] === true;
}The library keeps the symbol inside its own file. Only its two functions use it:
markProcessed(record)storestrueunder the symbol key.wasProcessed(record)checks whether thattrueis there.
Your app calls wasProcessed. It never needs to know the key. The app's own data looks the same as before, because the symbol key doesn't show up in Object.keys or in JSON.
const processed = Symbol("processed");
const record = { id: 3 };
record[processed] = true;
console.log(record.id);
console.log(record[processed]);It prints 3, then true. Line 4 reads the app's id. Line 5 reads the library's note.
Keep important data, like an ID, on string keys. Then it stays in the JSON when you save or send the object. Use symbol keys for notes that only your own code needs.
Remember: symbol keys are not secret. They stop accidental name clashes, and they stay out of JSON. But tools like Reflect.ownKeys can still find them.
Common misconceptions
“The description makes symbols equal.”
No. Symbol("id") === Symbol("id") is false. The description is only a label that helps you find bugs.
“Symbol properties are private.”
Ordinary lists skip them, but they are not private. Any code that has the symbol can read the property. And Object.getOwnPropertySymbols can find every symbol key.
“Use Symbol.for for everything.”
Use the registry only when code must share a symbol on purpose. For your own keys, use Symbol(). Then no other code can get your symbol by asking the registry for the same text.
“JSON lost my data.”
JSON.stringify always skips symbol-keyed properties. Keep important data on string keys. Or copy it to a string key yourself before you call JSON.stringify.
“Well-known symbols are just constants.”
They are symbol keys that JavaScript itself looks for. The function stored under Symbol.iterator decides how an object works with for...of.
Practice: symbols
5 EXERCISESPredict the three lines the console prints. Then check your answer.
const first = Symbol("id");
const second = Symbol("id");
console.log(first === second);
console.log(first === first);
console.log(typeof first);The first line prints false, because the two calls made two different symbols. The second prints true, because it compares one variable with itself. The third prints symbol, the type of first.
The program adds metadata under a symbol key. What does it print?
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 JSON has only the string key: {"name":"Ada"}.
Change the second line so it prints a label instead of throwing an error.
const id = Symbol("id");
console.log("Key: " + id);const id = Symbol("id");
console.log("Key: " + String(id));String(id) asks for the text directly. Then + joins two pieces of text. The fixed program prints Key: Symbol(id).
Imagine the two lines are in two separate files, called modules. Do they get the same symbol, or two different ones?
const moduleAKey = Symbol.for("course.userId");
const moduleBKey = Symbol.for("course.userId");
console.log(moduleAKey === moduleBKey);Both lines call Symbol.for("course.userId"). So both variables get the same symbol from the registry, and the comparison prints true.
This is a first look at iteration, which a later lesson covers. Predict what this object prints.
const colors = {
values: ["red", "blue"],
[Symbol.iterator]() {
return this.values[Symbol.iterator]();
}
};
console.log([...colors].join(","));The object has a function under [Symbol.iterator], so JavaScript can loop over it. Spreading it with ... gives red and blue. The console prints red,blue.
Quiz: check your understanding
7 QUESTIONSQuestion 1 of 5What does comparing two new 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 code gives two modules the same symbol?
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 this array check print?
Read the code, then predictconsole.log(typeof [][Symbol.iterator]);Choose an answer to see the explanation.
Key takeaways
Symbol()makes a new, unique primitive value every time.- The
descriptionis a label for people. It does not make two symbols equal. Symbol.forandSymbol.keyForuse a shared registry, for symbols you share on purpose.Object.keys,for...in, andJSON.stringifyskip symbol-keyed properties.Object.getOwnPropertySymbolsandReflect.ownKeyscan still find them.- Well-known symbols, such as
Symbol.iterator, are keys JavaScript looks for to change how an object behaves.
Remember the one-liner.
A symbol is a unique primitive value. You mostly use it as an object property key that can never clash with a string key or with another symbol.
Up next: Object to primitive conversion. It explains Symbol.toPrimitive in full.