Well-known symbols are built-in symbol values, stored as Symbol.iterator and so on, that the specification uses as property keys for hooks. A built-in algorithm looks up that key on your object. If you define it, your code runs. Symbols avoid collisions with string-named properties, and they are shared across realms (not in the global symbol registry).
| Symbol | Consulted by | You define |
|---|---|---|
iterator |
for…of, spread, destructuring, Array.from, Promise.all |
method returning an iterator |
asyncIterator |
for await…of |
method returning an async iterator |
toPrimitive |
ToPrimitive: +, template literals, relational operators |
(hint) => primitive, hint "string", "number" or "default" |
hasInstance |
instanceof |
static (value) => boolean |
toStringTag |
Object.prototype.toString |
string |
species |
methods that build a new instance, such as Array.prototype.map |
static getter returning a constructor |
isConcatSpreadable |
Array.prototype.concat |
boolean |
match, matchAll, replace, search, split |
the matching String.prototype methods, and IsRegExp |
method on any object |
unscopables |
with statements |
object of names to hide |
dispose, asyncDispose |
using and await using |
cleanup method |
Details interviewers probe: toPrimitive replaces valueOf/toString (when absent, OrdinaryToPrimitive calls them in an order that depends on the hint), and Date treats the default hint as "string". hasInstance is looked up first, so it overrides the prototype-chain check. toStringTag only changes the tag in [object X], and does not change typeof. species lets a subclass opt out of map and filter returning the subclass.