completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

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.

By the end, you can
  • 01
    Create unique keysMake symbols with Symbol(). Explain why two symbols with the same description are not equal.
  • 02
    Share when you mean toUse Symbol.for and Symbol.keyFor when two pieces of code need the same symbol.
  • 03
    Find 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.

Real-life analogyA symbol is a one-of-a-kind 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.

A symbol key on a userPop out in the code editor (opens in a new tab)JavaScript
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 user object with one string key, name.
  • Line 3 stores 42 on user, with the symbol as the key.
  • Line 4 reads with the same symbol, so it prints 42.
  • Line 5 prints undefined. user.userId looks for a key that is the text "userId". That text is not the symbol, even though the symbol's description says userId.

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 THROUGH

Call 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.

Step through: two symbols with the same description
Step 0 of 7Ready
Your turn: follow the blue line

Pick a description, predict each line, then step through.

Running in
  1. script
Next: line 1
const sameTag = Symbol("id");console.log(id === sameTag);console.log(id === id);console.log(typeof id);console.log(id.description);console.log(String(id));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Pick the description used on lines 1 and 2

Each choice starts a new run. Line 3 still prints false.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Two boxes, id and sameTag. Each holds a card that says Symbol("id"). Between them: id === sameTag is false, because they are two different symbols.id =‌=‌= sameTagSymbol("id")idfalseSymbol("id")sameTagsymbol 1symbol 2
Both cards show the same description, id. They are still two different symbols, so id === sameTag is false.
A description is a label, not the symbol

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.

A symbol with no descriptionPop out in the code editor (opens in a new tab)JavaScript
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 THROUGH

Many 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.

Asking for text vs letting + do it
Step 0 of 4Ready
Your turn: follow the blue line

A symbol turns into text only when you ask directly. Step through the two safe lines, then the line that throws.

Running in
  1. script
Next: line 1
console.log(String(id));console.log(id.toString());console.log("Key: " + id);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Left: String(id) turns the symbol in box id into the text "Symbol(id)". Right: "Key: " + id throws a TypeError.String(id)Symbol(id)id"Symbol(id)""Key: " + idSymbol(id)idTypeError
Ask for the text directly with String(id), and you get "Symbol(id)". Let + do it for you, and JavaScript throws a TypeError.
The text is only for showing

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 THROUGH

Sometimes 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.

Real-life analogyThe registry is a shared key cabinet

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.id key 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.

Local symbols vs registry symbols
Step 0 of 7Ready
Your turn: follow the blue line

Compare a local symbol with two registry symbols. Same text, different rules.

Running in
  1. script
Next: line 1
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));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
A box called registry holds one card, Symbol(app.id). Two name tags, sharedA and sharedB, both point at that same card. So sharedA === sharedB is true.Symbol(app.id)registrysharedAsharedBsharedA =‌=‌= sharedB
Both 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.

Registry keys are textPop out in the code editor (opens in a new tab)JavaScript
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.

Making a symbol with Symbol() or Symbol.for()
QuestionSymbol(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.

Ordinary lists skip symbol keys

INTERACTIVE

Most property keys are strings. A property whose key is a symbol is called a symbol-keyed property. Many everyday tools skip symbol-keyed properties:

  • Object.keys(obj) returns an array of the object's string keys.
  • A for...in loop visits the object's string keys one by one.
  • JSON.stringify(obj) turns the object into JSON text, so you can save or send it.

That makes symbol keys useful for metadata: extra notes about an object that are not part of its main data. Press each tool below and see what it finds.

List the keys of an object with a symbol key
Metadata under a symbol keyPop out in the code editor (opens in a new tab)JavaScript
const metadata = Symbol("metadata");const user = { name: "Ada" };user[metadata] = { role: "admin" }; Object.keys(user);// ["name"] JSON.stringify(user);// "{\"name\":\"Ada\"}" Object.getOwnPropertySymbols(user);// [Symbol(metadata)] Reflect.ownKeys(user);// ["name", Symbol(metadata)] user[metadata];// { role: "admin" }
Tool you pickedObject.keys(user)

Result

["name"]

The object still has both properties: name and Symbol(metadata). Each tool shows only the keys it looks for.

Try it yourself

Object.keys(user) skips the metadata under the symbol key. It shows only the string key, name.

The code adds a real symbol-keyed property. Press each tool, then compare its result with the code.
A cart called user holds two cards: name and [metadata]. In the console, Object.keys(user) gives only name. Reflect.ownKeys(user) gives name and Symbol(metadata).name[metadata]userconsoleObject.keys(user)["name"]Reflect.ownKeys(user)["name", Symbol(metadata)]
The object holds two properties. Object.keys lists only the string key, name. Reflect.ownKeys lists the symbol key too.

A symbol key can sit next to a string key with the same name. Neither one overwrites the other.

A symbol key next to a string key with the same namePop out in the code editor (opens in a new tab)JavaScript
const processed = Symbol("processed");
const record = { processed: "from app" };
record[processed] = true;
console.log(record.processed);
console.log(record[processed]);

It prints from app, then true. Line 4 uses a dot, so it reads the string key "processed". Line 5 uses square brackets with the saved symbol, so it reads the symbol key. They are two separate properties.

Each property also has a setting called enumerable. When it is true, the property is meant to show up when you list an object's properties. A property you add with = is enumerable. Object.getOwnPropertyDescriptor(obj, key) returns an object that describes one property, including this setting.

Object.keys skips a symbol key, even an enumerable onePop out in the code editor (opens in a new tab)JavaScript
const metadata = Symbol("metadata");
const user = {};
user[metadata] = "local";
console.log(Object.getOwnPropertyDescriptor(user, metadata).enumerable);
console.log(Object.keys(user).length);

The output is true, then 0. Line 4 shows that the symbol property is enumerable. Line 5 shows that Object.keys still finds no keys. It only returns string keys, so the symbol key is left out.

The table lists the tools you can use on an object. "Own" keys are keys stored on the object itself. "Inherited" keys come from another object it is linked to. Read each row as: does this tool show symbol keys, and what does it list?

Which tools show symbol keys
ToolDoes it show symbol keys?What it lists
Object.keys(obj)NoThe object's own string keys that are enumerable.
for...inNoEnumerable string keys, including inherited ones.
JSON.stringify(obj)NoOnly string keys, because JSON property names must be text.
Object.getOwnPropertySymbols(obj)YesOnly the object's own symbol keys.
Reflect.ownKeys(obj)YesAll own keys: string keys and symbol keys.
obj[symbolKey]YesIt reads one symbol property directly, if you have the symbol.

The property is not locked away. Three tools still reach it:

  • Object.getOwnPropertySymbols(obj) returns an array of the object's symbol keys.
  • Reflect.ownKeys(obj) returns string keys and symbol keys together.
  • obj[symbolKey] reads the property directly, if you have the symbol.
Which tools see the symbol key?
  • Object.keys(user)
  • for (const key in user)
  • JSON.stringify(user)
  • Object.getOwnPropertySymbols(user)
  • Reflect.ownKeys(user)
  • user[metadata]
Try it yourself
0 of 6 correct

Put each tool in a group: it skips the symbol key, it lists symbol keys, or it reads the property directly.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

A first look at well-known symbols

PREVIEW

JavaScript 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.

First look at well-known symbols
Built-in symbols at workPop out in the code editor (opens in a new tab)JavaScript
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);
Arrays can be looped over
typeof [][Symbol.iterator] → function

A for...of loop asks for the function stored under Symbol.iterator. Arrays have one.

Try it yourself

A for...of loop asks for the function stored under Symbol.iterator. Arrays have one.

These symbols are built into JavaScript. This is only a first look; later lessons cover looping and these symbols in full.
  • Symbol.iterator: a for...of loop looks for a function under this key. Arrays and strings have one, so for...of works on them. Plain objects don't have one.
  • Symbol.toStringTag: sets the name inside labels like [object Array]. These labels come from Object.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.

A library marks records with a symbol key
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) stores true under the symbol key.
  • wasProcessed(record) checks whether that true is 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.

A symbol key next to the app's dataPop out in the code editor (opens in a new tab)JavaScript
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 EXERCISES
Exercise 1 · Warm-upPredict symbol equality

Predict the three lines the console prints. Then check your answer.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const first = Symbol("id");
const second = Symbol("id");
console.log(first === second);
console.log(first === first);
console.log(typeof first);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeAdd metadata without changing JSON

    The program adds metadata under a symbol key. What does it print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const metadata = Symbol("metadata");
    const user = { name: "Ada" };
    user[metadata] = { role: "admin" };
    console.log(JSON.stringify(user));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFix the TypeError

      Change the second line so it prints a label instead of throwing an error.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const id = Symbol("id");
      console.log("Key: " + id);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeShare a key between modules

        Imagine the two lines are in two separate files, called modules. Do they get the same symbol, or two different ones?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const moduleAKey = Symbol.for("course.userId");
        const moduleBKey = Symbol.for("course.userId");
        console.log(moduleAKey === moduleBKey);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeIterable preview

          This is a first look at iteration, which a later lesson covers. Predict what this object prints.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const colors = {
            values: ["red", "blue"],
            [Symbol.iterator]() {
              return this.values[Symbol.iterator]();
            }
          };
          console.log([...colors].join(","));

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS
            Lesson quiz · 5 questionsScore: first tries count
            1. Question 1 of 5What does comparing two new symbols print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(Symbol("id") === Symbol("id"));

              Choose an answer to see the explanation.

            2. Question 2 of 5What is id.description for const id = Symbol("user")?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const id = Symbol("user");
              console.log(id.description);

              Choose an answer to see the explanation.

            3. Question 3 of 5Which code gives two modules the same symbol?

              Choose an answer to see the explanation.

            4. Question 4 of 5What does Symbol.keyFor(Symbol("local")) return?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(Symbol.keyFor(Symbol("local")));

              Choose an answer to see the explanation.

            5. Question 5 of 5Which tool lists symbol keys and string keys together?

              Choose an answer to see the explanation.

            Turning a symbol into text · 1 questionsScore: first tries count
            1. Question 1 of 1What happens here?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const id = Symbol("id");
              console.log("Key: " + id);

              Choose an answer to see the explanation.

            Built-in symbols · 1 questionsScore: first tries count
            1. Question 1 of 1What does this array check print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(typeof [][Symbol.iterator]);

              Choose an answer to see the explanation.

            Key takeaways

            • Symbol() makes a new, unique primitive value every time.
            • The description is a label for people. It does not make two symbols equal.
            • Symbol.for and Symbol.keyFor use a shared registry, for symbols you share on purpose.
            • Object.keys, for...in, and JSON.stringify skip symbol-keyed properties. Object.getOwnPropertySymbols and Reflect.ownKeys can 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.

            CompleteFrontend Clear concepts. Working examples.