All topics
ES6+advanced

Well-Known Symbols and Symbol.iterator

The set of built-in Symbol values that let user code hook into and customize core language behaviors like iteration, type coercion, and instanceof checks.

Well-known symbols are the hooks the language itself uses. Defining one changes how a built-in operation treats your object.

Well-known symbols are like universal remote-control buttons built into every device: if an appliance chooses to wire up the volume button to its own custom behavior, the remote automatically uses that wiring instead of doing nothing.

Key Concepts

1
    Symbol.iterator       for...of, spread, destructuring
    Symbol.asyncIterator  for await...of
    Symbol.toPrimitive    coercion to number or string
    Symbol.toStringTag    what Object.prototype.toString reports
    Symbol.hasInstance    instanceof
    Symbol.toStringTag
2
Symbol.iterator is the one you will implement.
    class Deck {
      #cards = [1, 2, 3];
      *[Symbol.iterator]() { yield* this.#cards; }
    }
Symbol.iterator
3
    [...new Deck()];               // [1, 2, 3]
    for (const c of new Deck()) { }
4
That is why arrays, strings, Map and Set work with for...of and a plain object does not.
MapSetfor...of
5
Symbol.toPrimitive controls coercion.
    class Money {
      constructor(a) { this.amount = a; }
      [Symbol.toPrimitive](hint) {
        return hint === 'number' ? this.amount : `Rs ${this.amount}`;
      }
    }
Symbol.toPrimitiveRs ${this.amount}
6
    const m = new Money(100);
    +m;              // 100
    `${m}`;          // 'Rs 100'
    m + 0;           // 100 -- hint is 'default'
${m}
7
Three hints: 'number', 'string' and 'default'. Without this, an object coerces via valueOf and toString, which is the older mechanism.
'number''string''default'valueOftoString
8
Symbol.toStringTag fixes type reporting.
    class Money { get [Symbol.toStringTag]() { return 'Money'; } }
    Object.prototype.toString.call(new Money());   // '[object Money]'
Symbol.toStringTag
9
Symbol.hasInstance customises instanceof.
    class Even {
      static [Symbol.hasInstance](n) { return n % 2 === 0; }
    }
    4 instanceof Even;     // true
Symbol.hasInstanceinstanceof
10
Clever, and easily confusing — the operator no longer means what readers expect.
11
The general point. These are extension points, not tricks. They are how a user-defined type participates in language features that would otherwise be reserved for built-ins.
12
In practice you will implement one or two. Symbol.iterator on a custom collection, occasionally Symbol.asyncIterator on a stream-like object. The rest are worth recognising in library code rather than reaching for.
Symbol.iteratorSymbol.asyncIterator
13
What the interviewer is probing.1. "What is a well-known symbol?" Probing: the concept. Stalls: "A constant." Moves up: a built-in symbol the language itself looks for, letting your object hook into syntax — Symbol.iterator, Symbol.asyncIterator, Symbol.toPrimitive.
14
2. "How do you control how an object converts to a primitive?" Probing: the hook. Stalls: "toString." Moves up: Symbol.toPrimitive, which receives a hint of number, string or default and takes precedence over valueOf and toString.
15
3. "What does Symbol.toStringTag change?" Probing: the output. Stalls: "Nothing." *Moves up:* the result of Object.prototype.toString, so a custom class can report [object MyType].
16
4. "Why are these symbols rather than strings?" Probing: the design. Stalls: "Style." *Moves up:* a string key could collide with a real property; a symbol cannot.