DEV Community

Cover image for What you need to know about Javascript's Implicit Coercion
Promise Tochi
Promise Tochi

Posted on • Updated on

What you need to know about Javascript's Implicit Coercion

Javascript's implicit coercion simply refers to Javascript attempting to coerce an unexpected value type to the expected type. So you can pass a string where it expects a number, an object where it expects a string etc, and it will try to convert it to the right type. This is a Javascript feature that is best avoided.

3 * "3" //9
1 + "2" + 1 //121

true + true //2
10 - true //9


const foo = {
  valueOf: () => 2
}
3 + foo // 5
4 * foo // 8

const bar = {
  toString: () => " promise is a boy :)"
}
1 + bar // "1 promise is a boy :)"


4 * [] // 0
4 * [2] // 8
4 + [2] // "42"
4 + [1, 2] // "41,2"
4 * [1, 2] // NaN

"string" ? 4 : 1 // 4
undefined ? 4 : 1 // 1
Enter fullscreen mode Exit fullscreen mode

Non-numeric values in numeric expressions

Strings

Whenever you pass a string as an operand in a numeric expression involving either of these operators: -, *, /, %, the number's conversion process is similar to calling the in-built Number function on the value. This is pretty straightforward, any string containing only numeric characters will be converted to it's number equivalent, but a string containing a non-numeric character returns NaN. Illustrated below,

3 * "3" // 3 * 3
3 * Number("3") // 3 * 3
Number("5") // 5

Number("1.") // 1
Number("1.34") // 1.34
Number("0") // 0
Number("012") // 12

Number("1,") // NaN
Number("1+1") // NaN
Number("1a") // NaN
Number("one") // NaN
Number("text") // NaN
Enter fullscreen mode Exit fullscreen mode

The case for the + operator

The + operator unlike other mathematical operators, performs two functions:

  1. Mathematical addition
  2. String concatenation

When a string is an operand of the + operator, Javascript instead of converting the string to a Number, converts the number to a string.


// concatenation
1 + "2" // "12"
1 + "js" // "1js"

// addition
1 + 2 // 3
1 + 2 + 1 // 4

//addition, then concatenation
1 + 2 + "1" // "31"
(1 + 2) + "1" // "31"

//concatenation all through
1 + "2" + 1 // "121"
(1 + "2") + 1 // "121"
Enter fullscreen mode Exit fullscreen mode

Objects

Most Javascript Object conversions usually result in [object Object], For example

"name" + {} // "name[object Object]
Enter fullscreen mode Exit fullscreen mode

Every javascript Object inherits a toString method, that is called whenever an Object is to be converted to a string. The return value of the toString method is used for such operations as string concatenation and mathematical expressions.

const foo = {}
foo.toString() // [object Object]

const baz = {
  toString: () => "I'm object baz"
}

baz + "!" // "I'm object baz!"
Enter fullscreen mode Exit fullscreen mode

When it's a mathematical expression, Javascript will attempt to convert the return value to a number, if it's not.

const foo = {
  toString: () => 4
}

2 * foo // 8
2 / foo // 0.5
2 + foo // 6
"four" + foo // "four4"

const baz = {
  toString: () => "four"
}

2 * baz // NaN
2 + baz // 2four

const bar = {
  toString: () => "2"
}

2 + bar // "22"
2 * bar // 4
Enter fullscreen mode Exit fullscreen mode

Array objects

The inherited toString method of Arrays work abit differently, it works in a way similar to calling the join method of an array without any arguments.

[1,2,3].toString() // "1,2,3"
[1,2,3].join() // "1,2,3"
[].toString() // ""
[].join() // ""

"me" + [1,2,3] // "me1,2,3"
4 + [1,2,3] // "41,2,3"
4 * [1,2,3] // NaN
Enter fullscreen mode Exit fullscreen mode

So when you pass an array where it expects a string, Javascript concatenates the return value of the toString method with the second operand. If it expects a number, it attempts to convert the return value to a number.

4 * [] // 0
4 / [2] // 2

//similar to
4 * Number([].toString())
4 * Number("")
4 * 0

//

4 / Number([2].toString())
4 / Number("2")
4 / 2
Enter fullscreen mode Exit fullscreen mode

True, False and ""

Number(true) // 1
Number(false) // 0
Number("") // 0

4 + true // 5
3 * false // 0
3 * "" // 0
3 + "" // "3"
Enter fullscreen mode Exit fullscreen mode

The valueOf method

It is also possible to define a valueOf method that will be used by Javascript whenever you pass an Object where it expects a string or numeric value.


const foo = {
  valueOf: () => 3
}

3 + foo // 6
3 * foo // 9
Enter fullscreen mode Exit fullscreen mode

When both the toString and valueOf methods are defined on an Object, Javascript uses the valueOf method instead.

const bar = {
  toString: () => 2,
  valueOf: () => 5
}

"sa" + bar // "sa5"
3 * bar // 15
2 + bar // 7
Enter fullscreen mode Exit fullscreen mode

The valueOf method is intended for Objects that are supposed to represent a numeric value.

const two = new Number(2)

two.valueOf() // 2
Enter fullscreen mode Exit fullscreen mode

Falsy and Truthy

I really wanted to make that falsy and trusy

Every Javascript value can be coerced into either true or false. Coercion into boolean true means the value is truthy. Coercion into boolean false means the value is falsy.

There are a handful of values in Javascript that return falsy values, they are:

  1. false
  2. 0
  3. null
  4. undefined
  5. ""
  6. NaN
  7. -0

Everything else is truthy,

if (-1) // truthy
if ("0") // truthy
if ({}) // truthy
Enter fullscreen mode Exit fullscreen mode

The above snippets are okay, but it is better practice to be explicit when trying to determine truthiness of a value. Basically, don't rely on Javascript's implicit coercion, even if you feel you know them perfectly.
Instead of the code snippet below,

const counter = 2

if (counter)
Enter fullscreen mode Exit fullscreen mode

Any of the below is better practice depending on your requirements

if (counter === 2)

//or

if (typeof counter === "number")
Enter fullscreen mode Exit fullscreen mode

This is because for example, you define a function that is supposed to work with numbers


const add = (number) => {
  if (!number) new Error("Only accepts arguments of type: number")
  //your code
}
Enter fullscreen mode Exit fullscreen mode

So if I call the add function with 0, I will always get an unintended error

add(0) // Error: Only accepts arguments of type: number

//better check

const add = (number) => {
  if (typeof number !== "number") new Error("Only accepts arguments of type: number")
  //your code
}

add(0) // no error

Enter fullscreen mode Exit fullscreen mode

NaN

NaN is a special numeric value that is not equal to itself.

NaN === NaN // false

const notANumber = 3 * "a" // NaN

notANumber == notANumber // false
notANumber === notANumber // false
Enter fullscreen mode Exit fullscreen mode

NaN is the only Javascript value that is not equal to itself. So you can check for NaN by comparing it to itself.

if (notANumber !== notANumber) // true
Enter fullscreen mode Exit fullscreen mode

ECMAScript 6 introduced a method for checking NaN, Number.isNaN

Number.isNaN(NaN) // true
Number.isNaN("name") // false
Enter fullscreen mode Exit fullscreen mode

Beware of the global isNaN function, it attempts to coerce it's argument before checking if it's NaN. For example,

isNaN("name") // true
isNaN("1") // false
Enter fullscreen mode Exit fullscreen mode

The global isNaN function should be avoided, the way it works is similar to the function below

const coerceThenCheckNaN = (val) => {
  const coercedVal = Number(val)
  return coercedVal !== coercedVal ? true : false
}

coerceThenCheckNaN("1a") // true
coerceThenCheckNaN("1") // false
coerceThenCheckNaN("as") // true
coerceThenCheckNaN(NaN) // true
coerceThenCheckNaN(10) // false
Enter fullscreen mode Exit fullscreen mode

That's most of implicit coercion. If I missed something, please chime in, in the comments below, and thanks for reading all the way.

Top comments (6)

Collapse
 
elarcis profile image
Elarcis

Only case where I like to use type coercion is with null.

There are only two values that == null : null and undefined. Same thing for undefined. So it's very useful for when you want to check if your variable holds an actual value and want to avoid an ugly (variable === null || variable === undefined), just do variable == null.

Collapse
 
promisetochi profile image
Promise Tochi

nice tip @elarcis

Collapse
 
kspeakman profile image
Kasey Speakman • Edited

The last thing I recall biting me in JS and I didn't see it mentioned above:

3 === new Number(3)  // false
3 === Number(3)      // true
Enter fullscreen mode Exit fullscreen mode

The first one is false because new creates a Number object instance which is compared by reference. Value does not equal object reference.

typeof new Number(3) // object
Enter fullscreen mode Exit fullscreen mode

The second one is true because it is a function which returns a value type.

typeof Number(3)     // number
typeof 3             // number
Enter fullscreen mode Exit fullscreen mode

This is a Javascript feature that is best avoided.

Type coercion is one of the worst parts of JS. I could not even count the number of hours I have wasted tracking down bugs because of this "feature". It is one of the main reasons I avoid the language.

Collapse
 
promisetochi profile image
Promise Tochi

Yeah, object wrappers. They almost never have an advantage over the normal primitives.

Those sort of bugs can truly be frustrating.

Collapse
 
jonrandy profile image
Jon Randy πŸŽ–οΈ • Edited

If you think it is best avoided, you aren't taking full advantage of what is possible in JS. JS is a weakly-typed language - embrace that fact, and stop trying to use it as if it were strongly typed. It's a pro, not a con.

I'm looking at you, #TypeScript !

Collapse
 
lexlohr profile image
Alex Lohr

I don't know if it actually fits into the implicit coercion stuff, since it is more about optimization, but since ES2015, the signed 64 bit float representation (52 bit of mantissa, 11 bit of binary exponent and 1 bit for the sign) of Number is now no longer the only available representation of numeric values (which was required in order to work with asm.js and Web Assembly).

Modern JS vms like v8 will therefore keep an extra internal flag if it knows that a number is an integer instead of a float throughout the scope and then run the faster non-floating-point calculations on them. To "coerce" a floating point number to such an integer for the rest of the scope, be sure to add | 0; to any calculation that may result in an odd result.