Lesson overview · Free interview practice
Symbols
Understand unique property keys and well-known symbols.
Topics in the full lesson
- Introduction to Symbols
- Creating and Using Symbols
- Well-Known Symbols
- Practical Applications of Symbols
- Best Practices
- Exercises
Practice Problems
Work through a question before revealing its explanation. These questions and answers are free; Premium adds the full lesson walkthrough, examples and implementation detail.
Completion marks record your own progress, not an automatically checked result. The task type does not determine whether it is optional.
Theory questions
What is a Symbol
What is a Symbol in JavaScript, and what guarantees does it provide? How does it differ from other primitives?
Take a moment to think about this before revealing the answer
Explain your reasoning or try an implementation before comparing answers.
Well-Known Symbols
What are well-known Symbols, and what do the most-asked ones (Symbol.iterator, Symbol.toPrimitive, Symbol.hasInstance) customize?
Take a moment to think about this before revealing the answer
Explain your reasoning or try an implementation before comparing answers.
Enumerating Symbol Properties
Why are Symbol-keyed properties hidden from Object.keys and for...in? How do you actually enumerate them?
Take a moment to think about this before revealing the answer
Explain your reasoning or try an implementation before comparing answers.
Symbols as Collision-Safe Property Keys
How do Symbols prevent property-name collisions? When should you use Symbol.for() instead of Symbol()?
Take a moment to think about this before revealing the answer
Explain your reasoning or try an implementation before comparing answers.
Coding practice
Customizing with Well-Known Symbols: `toPrimitive` / `toStringTag` / `hasInstance`
Implement and check your solution in the stated environment before revealing the answer.
Implement a Money class using Symbol.toPrimitive, Symbol.toStringTag, and a static Symbol.hasInstance method. Each hook must have an observable purpose.
Contract
new Money(amount, currency)stores publicamountandcurrencyproperties. Accept a finite number and a currency label of exactly three uppercase ASCII letters, such as'USD'. Zero and negative amounts are valid. This label rule is not a lookup of real currency codes.- Throw
TypeErrorfor an invalid constructor argument. Do not coerce inputs or provide defaults. Error wording is up to you. - For the
'number'and'default'coercion hints, returnamount. For the'string'hint, returnamount.toFixed(2) + ' ' + currency. Follow JavaScript'stoFixedformatting, including its behavior for very large numbers. Object.prototype.toString.call(value)must return'[object Money]'for a Money instance. This tag is separate fromString(value).candidate instanceof Moneymeans thatcandidateis a non-null object, not a function, with a finite numericamountand acurrencymatching the same label rule. Read properties normally, including inherited properties. Arrays with those properties count. Other objects and primitives returnfalse.- The shape check may execute getters and propagate their errors. It is for trusted values, not a safe inspection of hostile objects. A matching object does not acquire Money methods or coercion hooks.
Construction validates only the initial values. Assume valid fields remain unchanged during the coercion checks. You do not need to implement arithmetic, currency conversion, setters, or subclass-specific instance checks.
Review class instance members, static members, and the three coercion hints before attempting this core practice.
Local setup
Use Node.js 24.21.0 in a new local folder. Save starter.mjs and checks.mjs, then run:
node --version
cp starter.mjs solution.mjs
node checks.mjs
The starter deliberately fails. Implement the class in solution.mjs before expecting the checks to pass. No packages, browser, network, or real money are involved.
For TypeScript, save the alternate starter as starter.mts and the following as tsconfig.json. Use TypeScript 5.9.3 in strict mode.
{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"lib": ["ES2022"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"types": [],
"noEmitOnError": true,
"outDir": "build"
},
"files": ["solution.mts"]
}
tsc --version
cp starter.mts solution.mts
tsc --project tsconfig.json
node checks.mjs ./build/solution.mjs
The checks remain JavaScript so they can exercise runtime coercion, including price + 1. TypeScript does not infer numeric operator support from a custom coercion hook. Use explicit conversions such as Number(price) in typed application code. Consult the course's environment setup guidance if either tool is unavailable.
Starters
JavaScript starter: starter.mjs
export class Money {
constructor(amount, currency) {
throw new Error('Implement Money')
}
[Symbol.toPrimitive](hint) {
throw new Error('Implement coercion')
}
get [Symbol.toStringTag]() {
throw new Error('Implement the tag')
}
static [Symbol.hasInstance](candidate) {
throw new Error('Implement the shape check')
}
}
TypeScript starter: starter.mts
export class Money {
amount: number
currency: string
constructor(amount: unknown, currency: unknown) {
throw new Error('Implement Money')
}
[Symbol.toPrimitive](hint: 'number' | 'string' | 'default'): number | string {
throw new Error('Implement coercion')
}
get [Symbol.toStringTag](): string {
throw new Error('Implement the tag')
}
static [Symbol.hasInstance](candidate: unknown): boolean {
throw new Error('Implement the shape check')
}
}
Visible checks
Save these checks as checks.mjs.
import assert from 'node:assert/strict'
import test from 'node:test'
const { Money } = await import(process.argv[2] ?? './solution.mjs')
test('normal: all coercion hints and the separate object tag', () => {
const price = new Money(19.99, 'USD')
assert.equal(price.amount, 19.99)
assert.equal(price.currency, 'USD')
assert.equal(price[Symbol.toPrimitive]('number'), 19.99)
assert.equal(price[Symbol.toPrimitive]('default'), 19.99)
assert.equal(price[Symbol.toPrimitive]('string'), '19.99 USD')
assert.equal(+price, 19.99)
assert.equal(Number(price), 19.99)
assert.equal(price + 1, 20.99)
assert.equal('' + price, '19.99')
assert.equal(String(price), '19.99 USD')
assert.equal(`Total: ${price}`, 'Total: 19.99 USD')
assert.equal(Object.prototype.toString.call(price), '[object Money]')
})
test('edge: zero, negatives, and large finite amounts follow toFixed', () => {
for (const amount of [0, -0, -2.5, 1e21]) {
const price = new Money(amount, 'EUR')
assert.equal(Number(price), amount)
assert.equal(String(price), `${amount.toFixed(2)} EUR`)
}
})
test('error: constructor arguments are validated without conversion', () => {
for (const amount of ['5', NaN, Infinity, -Infinity, null, undefined]) {
assert.throws(() => new Money(amount, 'USD'), TypeError)
}
for (const currency of ['usd', '', 'US', 'USDD', 'ÉUR', ' USD', 'USD\n', 123, null]) {
assert.throws(() => new Money(5, currency), TypeError)
}
})
test('normal and edge: duck typing accepts inherited shapes, not just instances', () => {
const duck = { amount: 5, currency: 'EUR' }
assert.equal(new Money(5, 'EUR') instanceof Money, true)
assert.equal(duck instanceof Money, true)
assert.equal(Object.create(duck) instanceof Money, true)
assert.equal(Object.assign([], duck) instanceof Money, true)
assert.equal(Object.prototype.toString.call(duck), '[object Object]')
assert.equal(duck[Symbol.toPrimitive], undefined)
for (const candidate of [
null, undefined, 5, 'USD', {}, { amount: 5 },
{ amount: NaN, currency: 'USD' },
{ amount: Infinity, currency: 'USD' },
{ amount: '5', currency: 'USD' },
{ amount: 5, currency: 'usd' },
Object.assign(() => {}, duck),
]) {
assert.equal(candidate instanceof Money, false)
}
})
test('error: ordinary property reads can execute and propagate getters', () => {
const error = new Error('getter failed')
const candidate = {
get amount() { throw error },
currency: 'USD',
}
assert.throws(() => candidate instanceof Money, value => value === error)
})
Take a moment to think about this before revealing the answer
Explain your reasoning or try an implementation before comparing answers.
Explore the full Symbols material
Premium includes the complete lessons and implementation references. Free practice questions remain available without a subscription.