completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Well-known symbols

Hook into JavaScript language behavior with built-in symbols such as iterator, toPrimitive, toStringTag, hasInstance, species, string protocol hooks, and dispose.

By the end, you can
  • 01
    Name each hookMatch each well-known symbol to the built-in code that calls it.
  • 02
    Trace real callsPredict the hint, the arguments, and the output when JavaScript calls a symbol method.
  • 03
    Use them with careKnow which hooks are common, which are rare, and which you must check for before you use them.

Built-in keys that JavaScript looks for

A symbol is a special value you can use as a property key. Every symbol is unique, so it never clashes with a normal key like "name". You met symbols in the Symbols lesson.

JavaScript also comes with a few symbols that are already made. They live on Symbol, for example Symbol.iterator. These are called well-known symbols.

Here is why they matter. Some built-in code, like spread [...list] or a for...of loop, looks for a method at one of these keys. If your object has that method, JavaScript calls it for you. In this lesson, a method like that is called a hook.

Real-life analogyWell-known symbols are TV sockets

Think of a TV. The company that made it decided what the HDMI socket and the USB socket are for. You don't build a new socket. You plug in the cable that fits, and the TV does its job.

In real life: The HDMI, USB, and power sockets are already on the TV
In JavaScript: JavaScript already knows Symbol.iterator, Symbol.toPrimitive, and the others
In real life: You plug in the matching cable
In JavaScript: Your object has a method at the matching symbol key
In real life: The TV uses that socket for one known job
In JavaScript: A built-in operation calls that method
In real life: A label beside each socket tells you its job
In JavaScript: The symbol's name tells you which operation uses it

The table lists every hook in this lesson. Each row is one well-known symbol: the code that calls it, and when to use it.

Each well-known symbol and the code that calls it
SymbolWhich code calls it?When should you use it?
Symbol.iteratorfor...of, spread, destructuring, and Array.from call it.Use it for your own lists of things.
Symbol.asyncIteratorfor await...of calls it.Use it for values that arrive later, like pages from a server.
Symbol.toPrimitiveTemplate literals, math, +, and == call it.Use it only when the result is obvious. A plain method is often clearer.
Symbol.toStringTagObject.prototype.toString.call(value) reads it.Use it to give an object a clearer label in logs.
Symbol.hasInstancevalue instanceof Right calls it on Right.Rarely. A clear check function is easier to read.
Symbol.speciesSome array methods, like map, read it when they make a new array.Avoid it in new code. It makes code harder to check for safety and slower to run.
String method symbolsmatch, matchAll, replace, search, and split call them.Use them for small objects that act like a regex.
Symbol.disposeusing, DisposableStack, or your own finally block calls it.It is new. Check that the tools exist before you use them.

A hook lets your object work with built-in code, like an array does. Use hooks with care, though. A good hook gives the result a reader expects. A bad hook makes normal-looking code do something surprising.

Symbol.iterator and Symbol.asyncIterator

INTERACTIVE

The loop hooks are the easiest place to start. These four all call Symbol.iterator to get values from an object:

  • a for...of loop
  • spread, like [...cart]
  • destructuring, like const [first] = cart
  • Array.from(cart)

Symbol.asyncIterator does the same job for a for await...of loop. That loop waits for each value, because each one may arrive later.

The Iteration protocols lesson shows every detail of how this works. Here, just follow three steps: the loop finds the method at the symbol key, calls it, and takes the values it hands back.

A cart you can spreadPop out in the code editor (opens in a new tab)JavaScript
const cart = {
  *[Symbol.iterator]() { yield "tea"; yield "bread"; },
};
console.log([...cart].join(", "));

Line 2 adds a method to cart. The square brackets around Symbol.iterator let you use the symbol as the key. The star * makes the method a generator: a function that hands out values one at a time with yield. This one yields "tea", then "bread".

Line 4 spreads the cart with [...cart]. JavaScript calls the method, collects both values, and prints tea, bread. Without the method, spread throws a TypeError. It doesn't guess how to read the object.

Spread, written [...cart], calls the method at the key Symbol.iterator inside the cart. The method gives back tea, then bread.[...cart]spread asks[Symbol.iterator]cart"tea""bread"yields tea, then bread
Spread looks inside cart for the method at the key Symbol.iterator and calls it. The method hands back "tea", then "bread".
Pages that arrive laterPop out in the code editor (opens in a new tab)JavaScript
(async () => {
  const pages = { async *[Symbol.asyncIterator]() { yield 1; yield 2; } };
  const values = [];
  for await (const page of pages) values.push(page);
  console.log(values.join(", "));
})();

Line 2 gives pages an async generator at the key Symbol.asyncIterator. It yields the page numbers 1 and 2. Line 4 is a for await...of loop. It waits for each page and pushes it into values. Line 5 prints 1, 2. A normal for...of loop can't use this method; it only looks for Symbol.iterator.

Symbol hooks playground
One object and one class with symbol hooksPop out in the code editor (opens in a new tab)JavaScript
const gadget = {  name: "price tag",  value: 25,  [Symbol.toPrimitive](hint) { return hint === "string" ? "PriceTag" : 25; },  [Symbol.toStringTag]: "CatalogPrice",  [Symbol.iterator]: function* () { yield "label"; yield "value"; },  async *[Symbol.asyncIterator]() { yield "async label"; yield "async value"; },  [Symbol.isConcatSpreadable]: true,}; class PriceLike {  static [Symbol.hasInstance](value) {    return value?.value === 25;  }}
OperationsLive log
ReadyTurn hooks on or off, then press an operation.
Try it yourself

Press an operation to run normal JavaScript on gadget. The log shows which symbol method answered. Turn a hook off to see what happens without it: the normal result, or an error.

The code is kept short: one object and one class. The buttons run real JavaScript in this page. They never run code that you type.

Try this: turn Symbol.iterator off, then press spread. You don't get an empty array. You get an error, because spread needs that method. Then turn Symbol.asyncIterator off and press for await to see the same thing for the async loop.

Symbol.toPrimitive and Symbol.toStringTag

STEP THROUGH

A primitive is a plain value that is not an object, such as a string, a number, or a boolean. Sometimes JavaScript needs to turn an object into a primitive, for example to put it inside a template literal. The Object to primitive conversion lesson shows all the steps.

The first thing JavaScript tries is a method at the key Symbol.toPrimitive. The method gets one argument, called the hint. The hint is a word that says what kind of value the code wants:

  • A template literal, like `${price}`, sends "string".
  • Math, like price * 2, sends "number".
  • + with an object, and ==, send "default".
Step through the hint for Symbol.toPrimitive
Step 0 of 5Ready
Your turn: follow the blue line

Pick an expression, guess the hint, then step through the run.

Running in
  1. script
Next: line 1
  cents: 2500,  [Symbol.toPrimitive](hint) {    console.log("hint", hint);    return hint === "string" ? "$25.00" : 25;  },}; console.log(`${price}`);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Pick the expression on line 9

Picking a new expression starts the steps again from the beginning.

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.
The operation picks the hintPop out in the code editor (opens in a new tab)JavaScript
const price = {
  [Symbol.toPrimitive](hint) { return hint === "number" ? 25 : "$25"; },
};
console.log(`${price}`);
console.log(price * 2);

Line 2 is the hook. If the hint is "number", it returns 25. For any other hint, it returns the text "$25".

Line 4 is a template literal, so the hint is "string" and it prints $25. Line 5 multiplies, so the hint is "number". The method returns 25, and 25 * 2 prints 50.

Two rows. The template literal with price sends the hint string, the method returns the text $25, and $25 prints. The code price times 2 sends the hint number, the method returns 25, and 50 prints.hintreturns`${price}`"string""$25"prints $25price * 2"number"25prints 50
Each operation sends a hint word to the method. The template literal sends "string" and gets "$25"; multiplication sends "number", gets 25, and prints 50.

The hook must return a primitive. If it returns an object, JavaScript throws a TypeError.

Symbol.toStringTag does a different job. It sets the word inside a label like [object Map]. You see that label when you call Object.prototype.toString.call(value). It does not change what a template literal prints.

A clearer label for logsPop out in the code editor (opens in a new tab)JavaScript
const report = { [Symbol.toStringTag]: "Report" };
console.log(Object.prototype.toString.call(report));

Line 1 makes report with the tag "Report". Line 2 asks for the label and prints [object Report]. Without the tag, it would print [object Object]. This helps in logs.

Two different hooks

Want to change what `${value}` prints? Use Symbol.toPrimitive. Want Object.prototype.toString.call(value) to show a clearer [object Tag]? Use Symbol.toStringTag.

Symbol.hasInstance and Symbol.species

RARE HOOKS

value instanceof Thing asks: "Was value made by Thing?" Normally it checks whether Thing.prototype is in the value's prototype chain. The prototype chain is the list of objects JavaScript searches when it looks up a property.

But first, instanceof checks the right side for a method at Symbol.hasInstance. If it finds one, it calls Thing[Symbol.hasInstance](value) and uses that answer instead.

instanceof asks the classPop out in the code editor (opens in a new tab)JavaScript
class Adult {
  static [Symbol.hasInstance](user) { return user.age >= 18; }
}
console.log({ age: 20 } instanceof Adult);

Line 2 is a static method, so it belongs to the class Adult itself. It returns true when the age is 18 or more. Line 4 tests a plain object, { age: 20 }. That object was never made by Adult. Still, instanceof calls the method, and it prints true.

Symbol.species answers a different question. Some array methods, like map(), make a new array. If you call them on a subclass of Array, what kind of object should they make? A subclass is a class made with extends. A subclass can use Symbol.species to say: "Make a plain Array instead."

This hook exists so older code keeps working. New code should avoid it. It makes code harder to check for safety, and harder for JavaScript engines to speed up.

Species keeps the result a plain arrayPop out in the code editor (opens in a new tab)JavaScript
class Notes extends Array {
  static get [Symbol.species]() { return Array; }
}
const notes = new Notes("read", "write");
console.log(notes.map((note) => note.toUpperCase()) instanceof Notes);

Line 1 makes Notes, a subclass of Array. Line 2 is a static get: a getter on the class, which runs when the key is read. It returns Array. On line 5, map reads this key to choose what to make. So the new array is a plain Array, and line 5 prints false.

Species lab
An Array subclass with Symbol.speciesPop out in the code editor (opens in a new tab)JavaScript
class Bag extends Array {  static get [Symbol.species]() {    return Array;  }} const bag = new Bag("notebook", "pencil");const result = bag.map((item) => item.toUpperCase());console.log(result instanceof Bag);console.log(result instanceof Array);
Resultmap()
result instanceof Bagfalse
result instanceof Arraytrue
values["NOTEBOOK", "PENCIL"]
Try it yourself

Bag is a subclass of Array. Its Symbol.species getter returns Array, so map() makes a plain array, not a Bag.

This lab is here so you can read older libraries that use species. Most new classes should not use it.
Prefer a plain check

A clear function like isPriceLike(value) is easier to read than a surprising instanceof hook. Use these two symbols only when you build a library that must act like built-in objects. Don't use them for everyday app data.

Symbol.isConcatSpreadable and Symbol.unscopables

EDGE CASES

[].concat(value) joins values into a new array. It has its own rule:

  • An array is opened up: each of its items is added one by one.
  • A plain object is added as one item.

Symbol.isConcatSpreadable changes that rule for one value. Set it to true on an array-like object, and concat opens it up. An array-like object has number keys, like 0 and 1, and a length. Set it to false on an array, and concat adds the whole array as one item.

concat opens up an array-like objectPop out in the code editor (opens in a new tab)JavaScript
const row = {
  0: "tea", 1: "cake", length: 2,
  [Symbol.isConcatSpreadable]: true,
};
console.log(["menu"].concat(row).join(" / "));

Line 2 gives row the keys 0 and 1 and a length of 2. Line 3 sets the flag to true. So on line 5, concat adds tea and cake one by one, and it prints menu / tea / cake. Without the flag, it would add the whole object as one item.

Symbol.unscopables is mostly history. Very old code used a with statement, which lets you write includes instead of list.includes. Strict mode does not allow with, and this site never runs it.

When new array methods like includes were added, old with code could break. So arrays keep a list of names that with must skip. That list is at Array.prototype[Symbol.unscopables].

Unscopables viewer
Read the list of names that with skipsPop out in the code editor (opens in a new tab)JavaScript
const hiddenFromWith = Array.prototype[Symbol.unscopables];console.log(hiddenFromWith.includes);console.log(hiddenFromWith.find);console.log(hiddenFromWith.at);
Some entriesfrom this browser
includestrue
findtrue
attrue
first 8 namesat, copyWithin, entries, fill, find, findIndex, findLast, findLastIndex
Try it yourself

Symbol.unscopables is a list of array method names that old with statements must skip. This page shows the list. It never runs with.

Strict mode does not allow with, and new code should not use it. The list keeps old code working after new array methods were added.
The list of names that with skipsPop out in the code editor (opens in a new tab)JavaScript
const hidden = Array.prototype[Symbol.unscopables];
console.log(hidden.includes);
console.log(hidden.at);

Line 1 reads the list from the array prototype. Lines 2 and 3 check two names, and both print true: old with code skips includes and at. Don't write with in new code. You only need this symbol when you read old code.

String method hooks: match, matchAll, replace, search, split

INTERACTIVE

A regex (regular expression) is a pattern for finding text, like /cat/. You usually pass one to string methods such as replace. But you can pass your own object instead. Each of these string methods looks for a matching symbol method on the object you pass in:

  • match calls Symbol.match.
  • matchAll calls Symbol.matchAll.
  • replace calls Symbol.replace.
  • search calls Symbol.search.
  • split calls Symbol.split.

If the method is there, the string hands the work to it. In this lesson, an object like that is called a matcher.

A small match hookPop out in the code editor (opens in a new tab)JavaScript
const matcher = { [Symbol.match](text) { return text.includes("cat") ? "found" : "missing"; } };
console.log("cat nap".match(matcher));

Line 1 makes a matcher with a Symbol.match method. It returns "found" if the text has cat in it. Line 2 calls "cat nap".match(matcher). The string calls the method, so it prints found. The other four methods work the same way. Each example below uses one.

matchAll counts the matchesPop out in the code editor (opens in a new tab)JavaScript
const matcher = { flags: "g", [Symbol.matchAll](text) { return text.matchAll(/cat/g); } };
console.log([..."cat cat".matchAll(matcher)].length);

The matcher's method finds every cat with the real regex /cat/g. The g means "global": find every match, not just the first. The matcher also needs flags: "g", because matchAll checks for it first. Line 2 spreads the two matches into an array and prints its length, 2.

replace hands the work to the matcherPop out in the code editor (opens in a new tab)JavaScript
const matcher = { [Symbol.replace](text, replacement) { return text.replaceAll("cat", replacement); } };
console.log("cat nap".replace(matcher, "dog"));

The method gets two arguments: the whole text, "cat nap", and the new word, "dog". It swaps every cat for dog, so line 2 prints dog nap.

The code cat nap dot replace, with matcher and dog, calls the method at the key Symbol.replace inside the matcher. The method gets the text and dog, and returns dog nap."cat nap".replace(matcher, "dog")gets "cat nap" and "dog"[Symbol.replace]matcher"dog nap"returns this
The string doesn't do the work itself. It calls the matcher's Symbol.replace method with the text and "dog", then prints what the method returns: dog nap.
search has its own hookPop out in the code editor (opens in a new tab)JavaScript
const matcher = { [Symbol.search](text) { return text.indexOf("cat"); } };
console.log("scatter".search(matcher));

The method returns the position where cat starts in "scatter". Positions start at 0, so s is 0 and c is 1. Line 2 prints 1. Each hook works on its own, so you only write the ones you need.

split has its own hook tooPop out in the code editor (opens in a new tab)JavaScript
const matcher = { [Symbol.split](text) { return text.split("/"); } };
console.log("tea/cake".split(matcher).join(" + "));

The method cuts the text at each /, so "tea/cake" becomes ["tea", "cake"]. Line 2 joins the pieces and prints tea + cake. This method runs only for split, never for search or replace.

String method hooks lab
A matcher with all five string hooksPop out in the code editor (opens in a new tab)JavaScript
const matcher = {  needle: "cat",  flags: "g",  [Symbol.match](text) { return text.includes(this.needle); },  [Symbol.matchAll](text) { return text.matchAll(/cat/g); },  [Symbol.replace](text, replacement) { return text.replaceAll(this.needle, replacement); },  [Symbol.search](text) { return text.indexOf(this.needle); },  [Symbol.split](text) { return text.split(this.needle); },}; "cat nap cat".replace(matcher, "dog");
Resultreplace
dog nap dog
Try it yourself

Running "cat nap cat".replace(matcher) hands the work to the matcher's matching symbol method. Result: dog nap dog.

The matcher is a plain object, not a real regex. String methods still hand it the work, because it has the symbol methods.
Symbol.match makes an object act like a regex

If an object has a Symbol.match key with a truthy value, JavaScript treats the object like a regex. That has two effects:

  • startsWith and includes throw a TypeError if you pass it in, as they do for a real regex.
  • matchAll reads the object's flags. Without a g, it throws before your Symbol.matchAll method runs. That's why the lab's matcher has flags: "g".

Symbol.dispose and Symbol.asyncDispose

FEATURE DETECT

Some things must be cleaned up when you finish with them, even if your code fails halfway. Examples are an open file, a lock, or a subscription. JavaScript gives that cleanup method a standard key:

  • Symbol.dispose for cleanup that finishes right away.
  • Symbol.asyncDispose for cleanup you must wait for.
Real-life analogySymbol.dispose is a rental return rule

You rent a bike for a trip. The form says you must return it. If the trip goes wrong, you still bring the bike back.

In real life: The rental form says: return the bike when you're done
In JavaScript: A resource object has a [Symbol.dispose]() method
In real life: The trip goes wrong halfway
In JavaScript: The code in try throws an error
In real life: You still return the bike
In JavaScript: finally, using, or a stack still calls the cleanup method
Dispose feature lab
Call the cleanup method in finallyPop out in the code editor (opens in a new tab)JavaScript
const rental = {  open: true,  [Symbol.dispose]() {    this.open = false;    console.log("returned rental");  },}; try {  console.log("using rental");  throw new Error("trip failed");} finally {  rental[Symbol.dispose]();}console.log(rental.open);
Feature checksin this browser
Symbol.disposechecking
DisposableStackchecking
AsyncDisposableStackchecking
using declarationchecking
logs, with a catch addedusing rental | caught trip failed | returned rental | false
Try it yourself

The code uses try/finally, which works in every browser. The checks on the right test your browser: does it have the newer stack tools, and can it read the using keyword?

The checks run only after the page loads in your browser, because the server can't know what your browser has. This site never runs using in its own code.

In the lab's code, line 3 is the cleanup method. It sets open to false and prints returned rental. Line 10 prints using rental, then line 11 throws an error. A finally block always runs, even after an error, so line 13 calls the cleanup method. Then the error goes on, so line 15 never runs.

A console with three lines in order: using rental, returned rental, Error: trip failed. Notes beside them: line 10 runs, finally cleans up, then the error goes on.consoleusing rentalreturned rentalError: trip failed1. line 10 runs2. finally cleans up3. the error goes on
finally calls the cleanup method before the error leaves, so returned rental prints second. Then the error goes on, so line 15 never runs.

JavaScript also has newer tools that call the cleanup method for you:

  • the using and await using keywords
  • DisposableStack and AsyncDisposableStack, which collect several cleanups

At the time of writing, these are new. Some browsers and servers have the symbols but not these tools. So check that a tool exists before you use it. This is called feature detection. In a library, keep a try/finally version too, because it works everywhere.

Where you will use this

You will read these hooks more often than you write them. They show up in custom lists, form-checking libraries, price and date objects, data that arrives in pages, test helpers, and cleanup code. Before you write one, ask: "Do I want built-in code, like spread or a template literal, to use this object?"

  • Use Symbol.iterator when the value is a list of things.
  • Use Symbol.asyncIterator when the values arrive later.
  • Use the string method hooks for small matcher objects.
  • Use Symbol.toPrimitive only when the result is obvious, and write down what it does.
  • Use the dispose hooks for cleanup, with feature detection and a try/finally version.
A small class with three clear hooksPop out in the code editor (opens in a new tab)JavaScript
class Cart {
  constructor(items) { this.items = items; }
  get [Symbol.toStringTag]() { return "Cart"; }
  *[Symbol.iterator]() { yield* this.items; }
  [Symbol.toPrimitive](hint) { return hint === "number" ? this.items.length : `Cart(${this.items.length})`; }
}
const cart = new Cart(["tea", "bread"]);
console.log([...cart].join(", "));
console.log(Object.prototype.toString.call(cart));
console.log(+cart);

Cart is a normal class that keeps its things in items. It has three hooks:

  • Line 3 is a getter for the tag, so line 9 prints [object Cart].
  • Line 4 is the loop hook, so the spread on line 8 prints tea, bread.
  • Line 5 is the primitive hook. The + in front of cart on line 10 sends the "number" hint, so it prints 2, the number of items.

Each hook serves one built-in operation. The class still keeps its data in a plain items property that you can read directly.

Which symbol changes this behavior?
  • ${price} asks the object for a primitive
  • Object.prototype.toString.call(value) says [object Module]
  • [...range] gets values from your object
  • for await...of feed gets pages that arrive later
  • [].concat(box) opens the box up or adds it whole
  • subclass.map(...) chooses what kind of array to make
  • text.replace(matcher, "x") hands the work to the matcher
  • text.matchAll(matcher) hands the work to the matcher
Try it yourself
0 of 8 correct

Put each line of code in the group of symbols it uses.

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

Common misconceptions

"Symbol properties are private."

Symbol keys never clash with other keys. But any code can still find them with Object.getOwnPropertySymbols or Reflect.ownKeys.

"One hook changes every way an object is shown."

Symbol.toPrimitive, toString(), and Symbol.toStringTag each change a different thing.

"instanceof always checks the prototype chain."

Usually it does. But if the right side has a Symbol.hasInstance method, that method decides instead.

"Species is the modern way to build list classes."

It exists so older code keeps working. In new code, write plain methods that say what they return.

"Dispose runs by itself when an object is no longer used."

No. Something must call it: a finally block, a stack, or using.

Each row of this table shows something that is easy to get wrong, and what is true instead.

Hooks that are easy to mix up
What people thinkWhat is true
A symbol hook is a hidden event listener.It is just a property with a symbol key. JavaScript calls it only during one kind of operation.
toStringTag changes what a template literal prints.It changes only Object.prototype.toString.call(value). It does not change ${value}.
instanceof always checks the prototype chain.Usually it does. But the right side can have a Symbol.hasInstance method that decides instead.
species is a good habit for subclasses.It exists so older code keeps working. It makes code harder to check for safety and slower to run.

Practice: match the hook to the code

4 EXERCISES
Exercise 1 · Warm-upPredict the hints

Read the code. Type the four lines it prints, in order.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const badge = {
  [Symbol.toPrimitive](hint) {
    console.log(hint);
    return hint === "number" ? 7 : "badge";
  },
};
console.log(badge * 3);
console.log(`${badge}`);

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

    Exercise 2 · PracticeFix the concat bug

    The code prints 1, but you want 2. Which symbol makes concat add the items of row one by one?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const row = { 0: "Ada", 1: "Lin", length: 2 };
    console.log([].concat(row).length);

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

      Exercise 3 · PracticeRead a small string matcher

      Predict the text that the matcher code prints.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const onlyCats = {
        [Symbol.replace](text, replacement) {
          return text.replaceAll("cat", replacement);
        },
      };
      console.log("cat nap cat".replace(onlyCats, "dog"));

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

        Exercise 4 · ChallengeChoose a hook or a plain method

        For a shopping cart, would you add Symbol.toPrimitive so that cart + 0 gives the total? Or would you write a cart.totalCents() method? Explain your choice.

          Quiz: check your understanding

          8 QUESTIONS
          Lesson quiz · 8 questionsScore: first tries count
          1. Question 1 of 8Which code calls Symbol.iterator?

            Choose an answer to see the explanation.

          2. Question 2 of 8Which hints does this code log, and what does it print?

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

            Choose an answer to see the explanation.

          3. Question 3 of 8What does this tagged object print?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            const file = { [Symbol.toStringTag]: "Upload" };
            console.log(Object.prototype.toString.call(file));

            Choose an answer to see the explanation.

          4. Question 4 of 8In value instanceof Thing, which side gives the Symbol.hasInstance method?

            Choose an answer to see the explanation.

          5. Question 5 of 8What does concat print with this flag?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            const pair = { 0: "a", 1: "b", length: 2, [Symbol.isConcatSpreadable]: true };
            console.log(["start"].concat(pair).join("-"));

            Choose an answer to see the explanation.

          6. Question 6 of 8Why is Symbol.species rare in new code?

            Choose an answer to see the explanation.

          7. Question 7 of 8What does this matcher make search print?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            const matcher = {
              [Symbol.search](text) {
                return text.indexOf("cat");
              },
            };
            console.log("scatter".search(matcher));

            Choose an answer to see the explanation.

          8. Question 8 of 8Which statement about Symbol.dispose and using is true?

            Choose an answer to see the explanation.

          Key takeaways

          • Well-known symbols are built-in keys. Built-in code looks for methods at these keys and calls them.
          • Symbol.iterator lets for...of and spread read an object. Symbol.asyncIterator does the same for for await...of.
          • Symbol.toPrimitive gets the hint string, number, or default. Symbol.toStringTag sets the word in [object Tag].
          • Symbol.hasInstance, Symbol.species, Symbol.isConcatSpreadable, and Symbol.unscopables each change one small behavior. You rarely need them.
          • The string method hooks and the dispose hooks are useful. Check that dispose tools exist, and keep hooks unsurprising.

          Remember the one-liner.
          A well-known symbol is a built-in key that lets your object work with one specific piece of built-in JavaScript code.

          Up next: Decorators, reusable wrappers for classes, methods, and fields.

          CompleteFrontend Clear concepts. Working examples.