completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Symbols inside the engine

Learn how V8 stores JavaScript symbols, shares registry entries, uses well-known hooks, and hides private names and brands.

By the end, you can
  • 01
    Read what V8 prints about a symbolFind the map, the random hash, the label, and the flags. Explain why two symbols with the same label are still different.
  • 02
    Tell the kinds of symbol apartSay which symbols come from Symbol(), the Symbol.for registry, or the built-in list. Say which are shared with other realms, which live in read-only memory, and which can be WeakMap keys.
  • 03
    Connect symbols to how V8 stores objectsFollow symbol keys on objects, the map flag that marks interesting symbols, and the hidden symbols V8 uses for private fields and methods.

Every symbol is one of a kind

A symbol is a JavaScript value that is one of a kind. You make one with Symbol(). Each call gives you a new symbol. It is not equal to any other symbol.

You can give a symbol a description. It is a short text label, like "id" in Symbol("id"). The label helps people when they print the symbol. It does not change which symbol it is.

In your code, a symbol is a primitive: a simple value, like a number or a string. Inside the engine it looks different. The engine is the program that runs your JavaScript. This lesson uses V8, the engine inside Chrome and Node. V8 keeps each symbol as a small object in the heap, the memory area where the engine keeps objects. To check if two symbols are equal, V8 asks one question: are they the same object, at the same place in memory?

The main idea

A symbol is a one-of-a-kind value. In V8 12.4, the version inside Node 22, each symbol is a small Symbol object in the heap. That object holds:

  • a map: V8's note that says what kind of object this is,
  • a random number called a hash,
  • a few yes-or-no switches called flags,
  • the description, or undefined if there is none.
Same label, two different symbolsPop 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(String(first));

Lines 1 and 2 make two symbols with the same label, "id". Line 3 prints false, because they are two different symbols. Line 4 prints Symbol(id). String(symbol) turns a symbol into text when you ask for it by name, so this line is allowed.

Two carts, first and second. Each was made by Symbol("id") and each holds a card that says label: "id". Underneath, first === second gives false, because they are two separate symbols.Symbol("id")label: "id"firstSymbol("id")label: "id"secondfirst === secondfalse: two separate symbols
Each Symbol("id") call makes a new symbol. Both carry the same label, "id", but they are two separate things, so === gives false.
Real-life analogyTwo people both named Asha

Two people can both be named Asha. They are still two different people. The name helps you talk about them. It does not make them the same person. Symbols work the same way. The label is easy to see, but each symbol is its own thing.

In real life: Two people both named Asha
In JavaScript: Two Symbol('id') calls
In real life: They are still two different people
In JavaScript: The two symbols are not equal
In real life: The name helps you talk about them
In JavaScript: The label helps when you print or debug

To learn how symbols work in everyday code, read Symbols and Well-known symbols first. This lesson looks only inside V8. Its facts come from:

  • the V8 12.4.254.21 source code,
  • the ECMAScript spec, the official rules of JavaScript,
  • small test programs run in Node,
  • a post on the V8 blog about class fields.

What V8 stores inside a symbol

V8 is written in C++. It declares its symbol type as class Symbol : public Name in src/objects/name.h. Name is V8's type for anything that can name a property: a string or a symbol. A Name already has a place for the hash. The Symbol class adds two more fields:

  • flags_: a set of yes-or-no switches, packed into one number.
  • description_: the label. A comment in the code says String|Undefined, so it holds a string or undefined.

Each row below is one flag. Read across to see what V8 uses it for.

The yes-or-no flags on a V8 12.4 symbol
Flag name in V8What it marks
is_privateThe symbol is private to V8. V8's own hidden symbols and the symbols for private fields have it.
is_private_nameThe symbol stands for a private field like #x. Reading a missing private field throws an error instead of giving undefined.
is_private_brandThe symbol is a class's brand for private methods. Its value is V8's record for the class.
is_in_public_symbol_tableSymbol.for put the symbol in the registry. Symbol.keyFor can then give back its label as the key.
is_interesting_symbolThe symbol is a hook V8 watches for. In V8 12.4 that is Symbol.toPrimitive and Symbol.toStringTag.
is_well_known_symbolV8 uses it for one internal check. Only a few built-in symbols have it, not every built-in symbol.

V8 makes a new symbol in Factory::NewSymbolInternal, in src/heap/factory.cc. It does four things:

  1. It sets aside memory for the symbol, with symbol_map as its map. That map is read-only: it never changes.
  2. It calls GenerateIdentityHash to pick a random number, and stores it as the symbol's hash.
  3. It sets the description to undefined for now.
  4. It turns all the flags off.

The heap is split into areas. New objects usually go into young space, which the engine cleans often. Objects that live a long time move to old space. factory.h says that symbols made while your program runs go straight to old space (AllocationType::kOld). The factory does not allow young space for symbols at all. That is why Node's debug print says in OldSpace.

To see this yourself, you need Node and a special flag. Running node --allow-natives-syntax turns on V8's private helper functions. Their names start with %. %DebugPrint(value) prints what V8 knows about a value. These helpers only work in Node with that flag, so this code can't run in the browser.

Node only: print four symbols with %DebugPrintJavaScript
// Node-only: run with --allow-natives-syntax.function print(label, value) {  process.stdout.write("@@" + label + "\n");  %DebugPrint(value);} const first = Symbol("id");const second = Symbol("id");print("first", first);print("second", second);print("empty", Symbol());print("registered", Symbol.for("app.id"));

Lines 7 and 8 make two symbols with the same label. Lines 9 to 12 print four symbols: those two, one with no label, and one from Symbol.for. Each print shows the symbol's map, hash, flags, and description.

Lines you can trust

The lesson's test checks only text that stays the same on every run:

  • [Symbol] in OldSpace and type: SYMBOL_TYPE,
  • description: ... #id,
  • the flag names,
  • two different hash: lines for the two Symbol("id") values.

It never checks memory addresses or sizes. Those change from machine to machine. Node here has pointer compression off. Pointer compression is a trick that stores memory addresses in less space. Chrome has it on, and it uses a newer V8.

The shared registry: Symbol.for

Symbol.for(key) is a second way to get a symbol. It does not always make a new one. First it looks in the global symbol registry. The registry is one shared list of symbols, each stored under a text key.

  • If the key is in the list, you get the symbol that is already there.
  • If the key is not in the list, V8 makes a new symbol and adds it.

Inside V8, the steps are:

  1. builtins-symbol.cc calls isolate->SymbolFor(RootIndex::kPublicSymbolTable, key, false). An isolate is one running copy of V8, with its own heap.
  2. Isolate::SymbolFor internalizes the key. That means it swaps the key for V8's one shared copy of that string.
  3. It looks the key up in a RegisteredSymbolTable. This lookup table is declared in hash-table.h.
  4. If the key is missing, it makes a new symbol and adds it to the table.
  5. The symbol in the table has its is_in_public_symbol_table flag on.
A cart called registry holds one card, Symbol(app.id). Two calls to Symbol.for("app.id") on the right both point at that one card, so both get the same symbol.one shared listSymbol(app.id)registrySymbol.for("app.id")Symbol.for("app.id")both get the same symbol
The registry is one shared list of symbols, looked up by a text key. Every Symbol.for("app.id") call gets the same symbol back, so === gives true.

The next code uses Node's vm module. It runs code in a new realm: a fresh set of built-in objects and global variables, like a second web page. It also uses Symbol.keyFor(symbol), which gives back the key a symbol is stored under in the registry.

Node only: a new realm shares the registryJavaScript
// Node only: the vm module makes a new realm.import vm from "node:vm";console.log(vm.runInNewContext('Symbol.for("app.id")') === Symbol.for("app.id"));console.log(vm.runInNewContext("Symbol.iterator") === Symbol.iterator);console.log(Symbol.keyFor(Symbol.for("app.id")));console.log(Symbol.keyFor(Symbol("app.id")));

Line 3 prints true. The new realm gets the same Symbol.for("app.id") from the registry. Line 4 prints true too. Both realms get the same Symbol.iterator, which V8 keeps in read-only memory (more on that in the next section). The ECMAScript well-known symbols section says these built-in symbols are shared by all realms.

Line 5 prints app.id, the registry key. Line 6 prints undefined, because Symbol("app.id") was never put in the registry.

A WeakMap is a map that holds its keys weakly. If nothing else in the program uses a key, the engine may free that key and drop its entry. The spec's CanBeHeldWeakly step decides which values can be weak keys:

  • Objects can.
  • Symbols can, but only if they are not in the registry.

Any code can get a registry symbol back with Symbol.for, at any time. So a WeakMap entry for it could never safely be dropped. That is why a symbol from Symbol() and Symbol.iterator work as weak keys, but a symbol from Symbol.for throws.

Which symbols a WeakMap accepts as keysPop out in the code editor (opens in a new tab)JavaScript
for (const value of [Symbol("local"), Symbol.iterator, Symbol.for("shared")]) {  try {    new WeakMap().set(value, "stored");    console.log(String(value), "accepted");  } catch (error) {    console.log(String(value), error.name + ": " + error.message);  }}

Line 1 loops over three symbols. Line 3 tries to store each one as a WeakMap key. The first two print accepted. The registry symbol jumps to the catch block on line 5, and line 6 prints TypeError: Invalid value used as weak map key.

Built-in symbols live in read-only memory

Well-known symbols are the built-in symbols stored on Symbol, such as Symbol.iterator and Symbol.toPrimitive. JavaScript uses them as hooks: special keys that let an object change how a language feature treats it. For example, an object with a Symbol.iterator method works in a for...of loop.

V8 makes these symbols when it sets up the heap, before your code runs. setup-heap-internal.cc makes them with AllocationType::kReadOnly. So they go into read-only space: memory that never changes after setup. V8's own private and public symbols are made there too.

The lists of these symbols are in heap-symbols.h. They are part of V8's read-only roots, listed in roots.h. A root is a value V8 can always find straight away, by its number. Because these values never change, V8 can build them once and share them with every new realm. This is the snapshot idea from Startup performance.

Node only: print five built-in symbolsJavaScript
// Node-only: run with --allow-natives-syntax.function print(label, value) {  process.stdout.write("@@" + label + "\n");  %DebugPrint(value);} for (const name of [  "iterator",  "hasInstance",  "isConcatSpreadable",  "toPrimitive",  "toStringTag",]) {  print(name, Symbol[name]);}

Lines 7 to 13 print five built-in symbols. Every one of them prints in ReadOnlySpace. Now be careful: two different things are called "well-known".

  • In the ECMAScript spec, every built-in Symbol.* value is a well-known symbol.
  • In V8, is_well_known_symbol is a flag that V8 uses for one internal check. It is on for only a few symbols.

In Node 22 (V8 12.4), the print shows the flag on for Symbol.hasInstance, Symbol.isConcatSpreadable, and Symbol.toStringTag. It is off (0) for Symbol.iterator and Symbol.toPrimitive. Symbol.toPrimitive is still read-only. It has a different flag on, which you will meet in the next section.

V8 also has private symbols of its own. Your code can never get hold of them. The next probe shows one. Every object in V8 has a map that describes its shape. When an empty array gets a decimal number like 1.5, V8 moves the array to a new map. This move is called a transition, and V8 saves it under a key.

Node only: a hidden V8 symbol on an arrayJavaScript
// Node-only: run with --allow-natives-syntax.const array = [];array.push(1.5);%DebugPrint(array);

Lines 2 and 3 make an array and push 1.5 into it. The print shows a transition saved under the key <Symbol: (elements_transition_symbol)>. That key is one of V8's own private symbols. It is not a property your program can see or use.

Interesting symbols and a lookup shortcut

Some features must check if an object has a hook. For example, JSON.stringify checks each object for a toJSON method. Checking takes time, and most objects have no hook. So V8 keeps a shortcut.

V8 marks a few names as interesting:

  • the symbols Symbol.toPrimitive and Symbol.toStringTag. They get the is_interesting_symbol flag when V8 sets up the heap.
  • the strings toJSON and get. Name::IsInteresting counts them as interesting too.

When an object gets a property with an interesting name, V8 turns on a flag called may_have_interesting_properties. It means "this object might have a hook, so check it". V8 puts the flag on the object's map. Some objects keep their properties in a dictionary instead, a slower lookup table; then the flag goes on the dictionary. The code that sets it is in map-inl.h and js-objects.cc.

Node only: adding a hook turns on the map flagJavaScript
// Node-only: run with --allow-natives-syntax.function print(label, value) {  process.stdout.write("@@" + label + "\n");  %DebugPrint(value);} print("plain", {});print("toPrimitive", { [Symbol.toPrimitive]() { return 7; } });print("toStringTag", { [Symbol.toStringTag]: "Tagged" });

Line 7 prints a plain object. Its map does not have the flag. Lines 8 and 9 print objects with a Symbol.toPrimitive method and a Symbol.toStringTag property. Both maps show may_have_interesting_properties.

The note below sums up where V8 sets the flag and where it reads it.

Where V8 sets and checks the flagText
V8 12.4 source model:Name::IsInteresting() is true for symbols whose is_interesting_symbol bit is set,and also for the strings "toJSON" and "get". Map::AppendDescriptor and dictionaryinsertion copy that fact into may_have_interesting_properties. JSON.stringify walksthe receiver and prototypes with MayHaveInterestingProperties() before it pays forthe toJSON lookup.

The check is in json-stringifier.cc. MayHaveInterestingProperties looks at the map of the object and of each prototype, the objects it inherits from. If none of them has the flag, JSON.stringify skips the search for toJSON.

Turning an object into a primitive works differently. JSReceiver::ToPrimitive in js-objects.cc still calls Object::GetMethod to look for Symbol.toPrimitive.

Symbol.toPrimitive lets an object choose its own primitive value. JavaScript passes the method a hint: the kind of value it wants. +obj passes "number". A template string such as `${obj}` passes "string". The replay below walks through both. Its notes come from reading the V8 source. It is not a live recording from inside the engine.

Step through a Symbol.toPrimitive hook
Step 0 of 8Ready
Your turn: follow the blue line

Step through how a built-in symbol, Symbol.toPrimitive, lets an object choose its own primitive value.

Running in
  1. script
Next: line 1
  valueOf() {    return 10;  },};const protocol = {  [Symbol.toPrimitive](hint) {    return hint === "string" ? "tagged" : 7;  },};console.log(+plain);console.log(+protocol);console.log(`${protocol}`);
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.

Private fields use hidden symbols

A private field is a class field whose name starts with #, like #x. Only code inside the class can read it. The V8 blog post Faster initialization of instances with new class features says V8 stores private fields under private symbols. These are hidden symbols that your code can never get. V8 stores the field like any other property, but it never shows up as a normal property.

The same post explains how V8 sets up these fields fast. Adding a field moves the object to a new map. V8 remembers the private symbol together with that map change, so the next object of the class can be set up quicker.

Node only: print a private field and a private methodJavaScript
// Node-only: run with --allow-natives-syntax.class FieldBox {  #x = 1;  read() { return this.#x; }}class MethodBox {  #m() { return 1; }  call() { return this.#m(); }}%DebugPrint(new FieldBox());%DebugPrint(new MethodBox());

Line 10 prints a FieldBox, which has the private field #x (line 2). The print shows a property stored inside the object, under the key <Symbol: #x>.

Line 11 prints a MethodBox, which has the private method #m (line 7). A class with private methods puts a hidden brand on each object it makes. A brand is a mark that says "this class made this object". The print shows the brand as <Symbol: MethodBox>. Its value is V8's record for the class. V8's Runtime_AddPrivateBrand stores the brand as an own property that can't be listed, deleted, or changed.

The next code uses three tools. Each one is explained here:

  • Reflection means code that lists an object's own keys. Reflect.ownKeys lists all of them. Object.getOwnPropertySymbols lists only the symbol keys.
  • #x in value is a brand check. It asks: does value have the #x field?
  • A proxy, made with new Proxy(target, {}), is a wrapper object. It passes most actions on to the target object.
Reflection can't see a private fieldPop out in the code editor (opens in a new tab)JavaScript
class FieldBox {  #x = 1;  read() { return this.#x; }  static hasBrand(value) { return #x in value; }}const box = new FieldBox();console.log(Reflect.ownKeys(box).length);console.log(Object.getOwnPropertySymbols(box).length);console.log(FieldBox.hasBrand(box));console.log(FieldBox.hasBrand(new Proxy(box, {})));try {  box.read.call(new Proxy(box, {}));} catch (error) {  console.log(error.name + ": " + error.message);}try {  FieldBox.hasBrand(null);} catch (error) {  console.log(error.name + ": " + error.message);}
  • Lines 7 and 8 print 0 and 0. Neither tool can see #x.
  • Line 9 prints true. The brand check finds #x on box.
  • Line 10 prints false. The proxy is a different object, and it does not have #x.
  • Line 12 calls read() with the proxy as this. Reading this.#x throws a TypeError.
  • Line 17 checks #x in null. It throws, because null is not an object.
A cart called box holds a card that says #x: 1. A console window on the right runs Reflect.ownKeys(box) and gets an empty list, because #x is not listed.#x is really here#x: 1boxconsoleReflect.ownKeys(box)[]#x is not listed
V8 stores #x on the object under a hidden symbol. Reflect.ownKeys looks at the same object and still gives an empty list.

Symbols as property keys

A property key is the name of a property. V8 keeps property names as Name values. It stores them in two places:

  • descriptor arrays: the list of property names that belongs to a map,
  • property dictionaries: the slower lookup tables some objects use.

Name covers both strings and symbols. So a symbol can be a key just as it is. It is never turned into a string. The spec's OrdinaryOwnPropertyKeys step sets the order of an object's own keys:

  1. whole-number keys, like 1 and 2, smallest first,
  2. other string keys, in the order they were added,
  3. symbol keys, in the order they were added.
Step through four ways to list keys
Step 0 of 6Ready
Your turn: follow the blue line

Step through how symbol keys sit on an object, while most ways of listing keys skip them.

Running in
  1. script
Next: line 1
const record = { 2: "two", a: "letter", [secret]: "hidden", 1: "one" };console.log(Object.keys(record).join("|"));console.log(Object.getOwnPropertySymbols(record).map(String).join("|"));console.log(Reflect.ownKeys(record).map(String).join("|"));console.log(JSON.stringify(record));
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.

Object.keys, for...in, and JSON.stringify all skip symbol keys. To list only the symbol keys, use Object.getOwnPropertySymbols. To list every key, strings and symbols, use Reflect.ownKeys.

Top row: Reflect.ownKeys(record) gives 1, 2, a, Symbol(secret): numbers first, then text, then symbols last. Bottom row: Object.keys(record) gives 1, 2, a, and skips the symbol.Reflect.ownKeys(record)12aSymbol(secret)numbers firstthen textsymbols lastObject.keys(record)12askipped
Reflect.ownKeys lists every key: number keys first, then text keys, then symbol keys. Object.keys lists the same keys but skips the symbol.

Build and test a symbol

Type a label and pick how to make the symbol: Symbol() or Symbol.for(). The code then does four things with it:

  • compares it with a second symbol made the same way,
  • uses it as a property key,
  • tries it as a WeakMap key,
  • turns it into text in two ways.

The list of V8 flags under the results is a summary of what the Node debug prints show. It is not read live from V8.

Playground: make a symbol and use it
The playground codePop out in the code editor (opens in a new tab)JavaScript
const made = Symbol("app.id");const twin = Symbol("app.id");console.log(made === twin);const bag = { [made]: "value" };console.log(bag[made]);try {  new WeakMap().set(made, "stored");  console.log("weak key accepted");} catch (error) {  console.log(error.name + ": " + error.message);}try {  console.log("" + made);} catch (error) {  console.log(error.name + ": " + error.message);}console.log(String(made));
Real resultsSymbol()
two made the same way, equal?false
read through the symbol keyvalue
as a WeakMap keyweak key accepted
"" + symbolTypeError: Cannot convert a Symbol value to a string
String(symbol)Symbol(app.id)

V8 flags (summary)

label
app.id
where V8 keeps it
old space, in Node 22 (V8 12.4), for symbols made while the program runs
registry flag
off
WeakMap key allowed
yes, symbols outside the registry can be weak keys
Try it yourself
How to make it

Symbol makes a new symbol each time, outside the registry. The same label does not make the two equal. A WeakMap accepts it as a key.

The code on the left really runs. The V8 flag list is a summary of what the Node debug prints in the lesson's tests show.

Practical use

Symbols help when you need a key that can't clash with other keys, or a hook into a language feature. Pick the kind that fits:

  • Use Symbol() for a library's own data on other people's objects.
  • Use Symbol.for() only when separate pieces of code must find the same symbol by a shared text key.
  • Use a well-known symbol, like Symbol.iterator, when you want your object to work with a language feature, such as for...of.

Keep these tips in mind:

  • A symbol key from Symbol() can't clash with string keys that users of your code add.
  • Don't use the label to tell symbols apart. Two symbols with the same label are still different.
  • For data that must stay truly private, use private fields (#x). Ordinary symbol keys can still be listed with Reflect.ownKeys.
  • Don't guess that a symbol hook is slow. Measure first. V8 has the interesting-symbol flag so that objects without a hook stay fast.
Real-life analogyNames in a phone contact list

A shared contact can be found by everyone. A contact saved only on your phone is yours alone. A private note on a contact does not show in the normal contact list. Those are the three kinds of symbol in this lesson.

In real life: A shared contact everyone can find
In JavaScript: A symbol in the Symbol.for registry
In real life: A contact saved only on your phone
In JavaScript: A symbol from Symbol()
In real life: A private note on one contact
In JavaScript: The hidden symbol for a #x field

Common misconceptions

  • "The label decides which symbol it is." No. The label is only text. Two symbols are equal only if they are the very same symbol.
  • "Every Symbol.* value has V8's well-known flag." No. V8's is_well_known_symbol flag is for one internal check, and only a few symbols have it.
  • "Symbol keys are invisible." No. Object.keys and for...in skip them, but Reflect.ownKeys lists them.
  • "Registry symbols make good WeakMap keys." No. The spec does not allow symbols from Symbol.for as weak keys.
  • "Private fields are just normal symbols." No. V8 uses hidden symbols for them, but reflection and square brackets can't reach them.

Each row below is one kind of symbol. Read across to see where V8 keeps it and what you can do with it.

Four kinds of symbol that are easy to mix up
Kind of symbolWhere does V8 keep it?Is it shared with other realms?Can it be a WeakMap key?Can reflection list it?
Symbol()It is a symbol object in old space, made while the program runs.No. Each call makes a new one.Yes.Yes. Object.getOwnPropertySymbols lists it when it is a property key.
Symbol.for()It is a symbol object kept in V8's shared registry table.Yes, inside one running copy of V8.No. Registry symbols are not allowed.Yes, like any symbol key. Symbol.keyFor gives back its registry key.
A built-in symbol, like Symbol.iteratorIt is in read-only memory, made when V8 sets up the heap.Yes.Yes.Yes, if you put it on an object. V8's is_well_known_symbol flag is on for only a few of them.
The hidden symbol for #x or a brandIt is a private V8 symbol, made for a class's private fields or methods.No. It belongs to one class, not to the registry.Your code never gets it, so you can't try.No. Reflect.ownKeys and Object.getOwnPropertySymbols skip it.
Sort the symbols
  • Symbol('id')
  • Symbol.for('id')
  • Symbol.iterator
  • Symbol.toPrimitive
  • #x field key
  • Private method brand
  • 'id' description string
  • Object key 'toString'
Try it yourself
0 of 8 correct

Put each card in the group it belongs to.

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

Practice exercises

6 EXERCISES
Exercise 1 · Warm-upPredict: same label, two symbols

What does the comparison print?

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

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

    Exercise 2 · Warm-upRead the registry output

    Write the two outputs in order.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const a = Symbol.for("app.id");
    const b = Symbol.for("app.id");
    console.log(a === b);
    console.log(Symbol.keyFor(a));

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

      Exercise 3 · PracticeFind the symbol a WeakMap rejects

      Which kind of symbol does a WeakMap reject as a key?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      for (const value of [Symbol("local"), Symbol.iterator, Symbol.for("shared")]) {
        try {
          new WeakMap().set(value, "stored");
          console.log(String(value), "accepted");
        } catch (error) {
          console.log(String(value), error.name + ": " + error.message);
        }
      }

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

        Exercise 4 · PracticeRemember the key order

        What does the Reflect.ownKeys line print?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const secret = Symbol("secret");
        const record = { 2: "two", a: "letter", [secret]: "hidden", 1: "one" };
        console.log(Object.keys(record).join("|"));
        console.log(Object.getOwnPropertySymbols(record).map(String).join("|"));
        console.log(Reflect.ownKeys(record).map(String).join("|"));
        console.log(JSON.stringify(record));

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

          Exercise 5 · PracticeCan reflection see a private field?

          How many keys do Reflect.ownKeys and Object.getOwnPropertySymbols find?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          class FieldBox {
            #x = 1;
            read() { return this.#x; }
            static hasBrand(value) { return #x in value; }
          }
          const box = new FieldBox();
          console.log(Reflect.ownKeys(box).length);
          console.log(Object.getOwnPropertySymbols(box).length);
          console.log(FieldBox.hasBrand(box));
          console.log(FieldBox.hasBrand(new Proxy(box, {})));
          try {
            box.read.call(new Proxy(box, {}));
          } catch (error) {
            console.log(error.name + ": " + error.message);
          }
          try {
            FieldBox.hasBrand(null);
          } catch (error) {
            console.log(error.name + ": " + error.message);
          }

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

            Exercise 6 · ChallengePick the safe way to turn a symbol into text

            Which way of turning the symbol into text works?

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            const tag = Symbol("tag");
            try {
              console.log("" + tag);
            } catch (error) {
              console.log(error.name + ": " + error.message);
            }
            console.log(String(tag));

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

              Quiz: check your understanding

              9 QUESTIONS
              Symbols inside the engine quiz · 9 questionsScore: first tries count
              1. Question 1 of 9Inside V8, what makes one symbol different from another?

                Choose an answer to see the explanation.

              2. Question 2 of 9What does this 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.

              3. Question 3 of 9What does the registry code print?

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

                Choose an answer to see the explanation.

              4. Question 4 of 9Which symbol in this code can't be a WeakMap key?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                for (const value of [Symbol("local"), Symbol.iterator, Symbol.for("shared")]) {
                  try {
                    new WeakMap().set(value, "stored");
                    console.log(String(value), "accepted");
                  } catch (error) {
                    console.log(String(value), error.name + ": " + error.message);
                  }
                }

                Choose an answer to see the explanation.

              5. Question 5 of 9What does the Reflect.ownKeys line (line 5) print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const secret = Symbol("secret");
                const record = { 2: "two", a: "letter", [secret]: "hidden", 1: "one" };
                console.log(Object.keys(record).join("|"));
                console.log(Object.getOwnPropertySymbols(record).map(String).join("|"));
                console.log(Reflect.ownKeys(record).map(String).join("|"));
                console.log(JSON.stringify(record));

                Choose an answer to see the explanation.

              6. Question 6 of 9In V8 12.4, which symbols have the is_interesting_symbol flag?

                Choose an answer to see the explanation.

              7. Question 7 of 9What do the two lines print for this private field?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                class Box { #x = 1; }
                const box = new Box();
                console.log(Reflect.ownKeys(box).length);
                console.log(Object.getOwnPropertySymbols(box).length);

                Choose an answer to see the explanation.

              8. Question 8 of 9Which way of turning a symbol into text throws on purpose?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const tag = Symbol("tag");
                try {
                  console.log("" + tag);
                } catch (error) {
                  console.log(error.name + ": " + error.message);
                }
                console.log(String(tag));

                Choose an answer to see the explanation.

              9. Question 9 of 9In the replay code, what does +protocol give?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const plain = {
                  valueOf() {
                    return 10;
                  },
                };
                const protocol = {
                  [Symbol.toPrimitive](hint) {
                    return hint === "string" ? "tagged" : 7;
                  },
                };
                console.log(+plain);
                console.log(+protocol);
                console.log(`${protocol}`);

                Choose an answer to see the explanation.

              Key takeaways

              • In V8, a symbol is a small object in the heap. Two symbols are equal only if they are the same object. The label does not decide it.
              • A symbol made while your program runs goes straight to old space, and it gets a random hash when it is made.
              • Symbol.for uses V8's shared registry. Symbols from the registry can't be WeakMap keys.
              • Built-in symbols live in read-only memory. V8's is_well_known_symbol flag is on for only a few of them.
              • Private fields and private methods use hidden symbols. Reflection can't list them.

              One-liner.
              A symbol is a small one-of-a-kind object with a label. V8 can share it through the registry or hide it for a private field, and it uses flags to keep symbol hooks fast.

              Up next: Hidden classes & object shapes. There, the maps, descriptor arrays, and transitions you met here become the main story.

              CompleteFrontend Clear concepts. Working examples.