This section focuses on how JavaScript evaluates values of different types, with special attention to explicit vs implicit coercion and the rules behind:
==(Abstract Equality Comparison)Object.is(SameValue semantics)+(string concatenation vs numeric addition)- Boolean contexts (
if,!,&&,||)
The goal is not memorization: it’s building a mental execution model so you can predict behavior and implement the exercises deterministically.
- JavaScript is dynamically typed: variables have no fixed type, values do.
- Types are resolved at runtime, often implicitly via operators.
- Many operators (
==,+,!, arithmetic, conditionals) force values through coercion steps.
When a value is evaluated in a boolean context, JavaScript applies ToBoolean.
Falsy values are only:
false0,-00n""nullundefinedNaN
Everything else is truthy, including:
[] // truthy
{} // truthy
"0" // truthy
"false" // truthy
function () {} // truthyThis is why:
![] === falseJavaScript has two equality operators with different intent:
===(Strict Equality): compares without coercion (mostly).==(Abstract Equality): applies coercion via well-defined rules.
Important:
==is deterministic, not random - but error-prone.- Senior engineers should be able to explain the coercion, not just avoid the operator.
When JavaScript evaluates left == right, it runs the spec algorithm called Abstract Equality Comparison.
Spec link: https://tc39.es/ecma262/#sec-abstract-equality-comparison
These rules cover the coercions used in this section’s exercises/tests.
null == undefined // truenullonly equalsundefined- No numeric/string coercion happens (terminal rule)
If both operands have the same type, compare without coercion:
0 == 0 // true
"a" == "a" // trueIf either side is boolean, it is converted to a number:
false → 0
true → 1
0 == false // trueIf one side is an object (arrays included) and the other is a primitive (string/number), the object is converted to a primitive.
Typical order:
valueOf()- then
toString()
Examples:
[] -> "" // array toString
{} -> "[object Object]"If one side is a string and the other is a number, the string is converted to a number:
"" -> 0
"0" -> 0
"
" -> 0Once both sides have the same type, the final comparison happens.
[] == ![]![]→false- boolean → number →
0 []is object → ToPrimitive →""- string → number →
0 - same type → compare
0 == 0→true
A common “interview-grade” version:
- If value is not object-like, it is already primitive.
- Else call
valueOf(); if it returns a primitive, use it. - Else call
toString(); if it returns a primitive, use it. - Else throw (cannot produce a primitive).
This is the core of + semantics and some == paths.
Useful facts that show up in tests:
Number(" 2 ") // 2
Number("
") // 0
Number("") // 0
Number("x") // NaN
Number(true) // 1
Number(false) // 0
Number(null) // 0
Number(undefined) // NaN (but many exercises disallow undefined explicitly)
Number(0) // 0
Number(-0) // -0 (distinct from 0 in some comparisons)
Number(Infinity) // Infinity (often rejected by "finite number" guards)Mostly relevant when + chooses concatenation (string present):
String(null) // "null"
String(undefined) // "undefined"
String(1) // "1"Object.is(a, b) uses SameValue semantics. It differs from === in two key cases:
NaNis the same asNaN-0is not the same as0
Examples:
Object.is(NaN, NaN) // true
NaN === NaN // false
Object.is(-0, 0) // false
-0 === 0 // trueSpec link: https://tc39.es/ecma262/#sec-samevalue
a + b is not “always numeric addition”.
High-level rule (subset used in these exercises):
- Apply ToPrimitive to both sides.
- If either primitive is a string, do string concatenation.
- Otherwise, do numeric addition using
Number(...)coercion.
Examples:
1 + "2" // "12" (string present)
"1" + 2 // "12"
" 2 " + "
" // " 2 0" (still concat, not numeric)
true + 2 // 3 (Number(true)=1)
null + 1 // 1 (Number(null)=0)This is exactly what you implement in plusSemantics(a, b) without using +.
value instanceof Array depends on prototypes and can be fooled:
- You can make a non-array pass by changing its prototype.
- You can make a real array fail by removing its prototype.
Use Array.isArray(value) as the safe check.
A “plain object” is typically:
- object literal
{}or Object.create(null)
But not: arrays, functions, dates, regexes, class instances.
Practical detection approach:
- ensure
typeof value === "object"andvalue !== null - reject arrays (
Array.isArray) - check prototype is either
Object.prototypeornull
In these exercises:
- valid numbers: finite numbers (reject
NaN,Infinity,-Infinity) - valid numeric strings: strings that have non-empty trimmed content and whose
Number(trimmed)is finite - reject booleans,
null,undefined, and0n(BigInt)
The exercises intentionally force you to:
- trace coercion step-by-step (
==) - implement SameValue without
Object.is - perform explicit numeric coercion (rejecting invalid inputs)
- build robust type guards (arrays, plain objects, numeric strings)
- emulate interview-grade
+semantics (ToPrimitive + concat vs add)
If you can explain why JavaScript produced a value, and you can reimplement the logic explicitly, you understand coercion.