cf.completefrontendCode editorOpen lab
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 keysUse Symbol() and explain why matching descriptions do not make symbols equal.
  • 02
    Share when you mean toReach for Symbol.for and Symbol.keyFor when two pieces of code need the same symbol.
  • 03
    Inspect 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.

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

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

Step through symbol uniqueness
Step 0 of 7Ready
Your turn: follow the blue line

Pick a description, predict the comparisons, then step through real symbol values.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const sameTag = Symbol("id");console.log(id === sameTag);console.log(id === id);console.log(typeof id);console.log(id.description);console.log(String(id));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Change the description text used on lines 1 and 2

Changing the description starts a fresh recorded run. The equality result never changes.

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.
Descriptions are labels, not identities

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 THROUGH

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

Explicit conversion vs implicit conversion
Step 0 of 4Ready
Your turn: follow the blue line

Symbols need explicit string conversion. Step through the safe lines, then the failing one.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
console.log(String(id));console.log(id.toString());console.log("Key: " + id);
CallStoreChangeResultRun = next line. 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.
Use the string only for display

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 THROUGH

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

Real-life analogyThe registry is a shared key cabinet

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.

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

Compare a local symbol with a shared registry symbol. Same words, different rules.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
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));
CallStoreChangeResultRun = next line. 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.
Symbol() vs Symbol.for()
QuestionSymbol(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 usePrivate-ish object keys and collision-free metadata.Shared protocols between modules or packages.

Symbol-keyed properties are hidden from ordinary lists

INTERACTIVE

Object property keys are usually strings. Symbols are allowed too, which gives you a useful trick: attach metadata without showing up in everyday inventories such as Object.keys, for...in, or JSON.stringify.

Inspect an object with a symbol drawer
Symbol-keyed metadataPop 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" }
Current inspectionObject.keys(user)

Result

["name"]

The object still has both drawers: name and Symbol(metadata). You just chose a tool that sees one set of keys.

Try it yourself

Object.keys(user) skips the symbol-keyed metadata and shows only the ordinary string key.

The code uses a real symbol-keyed property. Try each inspection method, then compare the result with the highlighted expression.
Shows up or stays hidden?
InspectionShows symbol keys?What it lists
Object.keys(obj)NoOnly string keys that are enumerable
for...inNoEnumerable string keys, including inherited ones
JSON.stringify(obj)NoJSON property names must be strings
Object.getOwnPropertySymbols(obj)YesOwn symbol keys only
Reflect.ownKeys(obj)YesOwn string keys and own symbol keys
obj[symbolKey]YesDirect access works when you have the symbol

The property is not locked. Object.getOwnPropertySymbols finds symbol keys, Reflect.ownKeys finds string and symbol keys, and direct bracket access works if you have the symbol.

Shows up or hidden?
  • 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

Sort each inspection tool by whether it skips the symbol key, finds symbol keys, or 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 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.

First look at well-known symbols
Built-in symbol hooksPop 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 opt into iteration
typeof [][Symbol.iterator] → function

A for...of loop asks for the method stored at Symbol.iterator. Arrays have one.

Try it yourself

A for...of loop asks for the method stored at Symbol.iterator. Arrays have one.

These names are built into JavaScript. This is only a preview; later lessons cover iteration and well-known symbols in depth.
  • Symbol.iterator is why for...of works on arrays and strings. Plain objects do not have it by default.
  • Symbol.toStringTag customizes labels such as [object Array].
  • Symbol.toPrimitive controls 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.

A realistic metadata tag
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 EXERCISES
Exercise 1 · Warm-upPredict symbol equality

Predict the three console lines before checking the 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

    What does the program print after adding symbol-keyed metadata?

    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

      Rewrite the second line so it prints a label instead of throwing.

      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 separate modules. Do they share the same key or create separate keys?

        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 preview of the iteration protocols. Predict what the custom iterable 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 fresh 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 expression gives two modules the same symbol value?

              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.

            Symbol conversion check · 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.

            Final symbol check · 1 questionsScore: first tries count
            1. Question 1 of 1What does the array iterator 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() creates a unique primitive value every time.
            • The description helps humans read logs; it does not make symbols equal.
            • Symbol.for and Symbol.keyFor work with a shared registry for intentionally shared keys.
            • Symbol-keyed object properties are skipped by Object.keys, for...in, and JSON.stringify, but symbol-aware tools can find them.
            • Well-known symbols such as Symbol.iterator are 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.

            CompleteFrontend Clear concepts. Working examples.