Types and conversion

Convert between types on purpose, and stop being surprised by comparisons.

03of 32
≈ 12Min read
8Parts

JavaScript converts, whether you asked or not

Most operators will coerce their operands to make an operation possible. That is convenient about half the time and the source of the language's worst reputation the rest. The fix is not to memorise the rules — it is to convert explicitly and compare with ===.

the famous ones
"5" + 3      // "53"   — + prefers strings
"5" - 3      // 2      — every other operator prefers numbers
[] + {}      // "[object Object]"
[] == false  // true
null >= 0    // true, but null > 0 and null == 0 are both false

Converting on purpose

to a number
Number("12.5")     // 12.5
Number("12px")     // NaN     — strict
Number("")         // 0       ← watch this one
Number(null)       // 0
Number(undefined)  // NaN
Number(true)       // 1

parseInt("12px", 10)   // 12   — stops at the first non-digit
parseFloat("1.5rem")   // 1.5

+"12.5"            // 12.5  — terse, and easy to miss when reading
Always pass the radix to parseInt

parseInt("08") is 8 today, but the two-argument form is unambiguous and costs three characters.

To a string, and to a boolean

the other two directions
String(12.5)       // "12.5"
String(null)       // "null"
(12.5).toFixed(2)  // "12.50" — a STRING
`${12.5}`          // "12.5"

Boolean("")        // false
Boolean("0")       // true    ← a non-empty string is truthy
!!value            // the same thing, shorter

Truthiness: the eight falsy values

memorise this list; everything else is truthy
false
0
-0
0n
""
null
undefined
NaN

// truthy, and people get caught by all of these:
[]  {}  "0"  "false"  " "  function(){}
The empty-array trap

if (list) is true even for an empty array. Test list.length when you mean "has items".

=== always

== is transitive-breaking: 0 == "" and 0 == "0" are both true while "" == "0" is false. There is no mental model that makes that safe.

The one acceptable use of == is x == null, which is true for exactly null and undefined — a genuinely useful test.

ComparisonResultWhy
0 == ""trueBoth become 0.
0 == "0"trueBoth become 0.
"" == "0"falseBoth are strings, so no conversion.
null == undefinedtrueA special case in the spec.
null === undefinedfalseDifferent types.
NaN === NaNfalseNaN is not equal to anything, including itself.
[] == falsetrue[] → "" → 0, false → 0.

NaN

NaN spreads: once it appears, every arithmetic result downstream is NaN. When a total comes out NaN, work backwards to the first input that was not a number.

not a number, and not equal to itself
0 / 0                 // NaN
Number("abc")         // NaN
Math.sqrt(-1)         // NaN

NaN === NaN           // false
Number.isNaN(NaN)     // true  — use this
isNaN("abc")          // true  — the GLOBAL one coerces first, so it lies
Number.isNaN("abc")   // false — "abc" is not the NaN value
Object.is(NaN, NaN)   // true

Checking a type

the right tool for each
typeof x === "string"
typeof x === "number" && Number.isFinite(x)
typeof x === "function"
Array.isArray(x)                 // typeof [] is "object"
x === null                       // typeof null is "object"
x instanceof Date
Object.prototype.toString.call(x)  // "[object Date]" — the last resort

Reading the numbers you show people

Thousands separators, decimal marks and currency positions differ by locale. Intl knows all of them; a template literal does not.

never build these by hand
new Intl.NumberFormat("az").format(1234.5);              // "1.234,5"
new Intl.NumberFormat("en-GB", {
  style: "currency", currency: "GBP",
}).format(1234.5);                                       // "£1,234.50"

Practice