JavaScript Coercion

Coercion is a noun, and it means, "the action or practice of persuading someone to do something by using force". Section 7 in the EcmaScript Specification says the following "The ECMAScript language implicitly performs automatic type conversion as needed".

Coercion in JavaScript with a yellow background

In this article, we will look at coercion in JavaScript, both explicit and implicit. But first, we will look at types and values in JavaScript so we can more easily understand coercion.

JavaScript types and values

JavaScript has 7 built-in primitive types according to MDN:

  • boolean
  • null
  • undefined
  • number
  • bigint
  • string
  • symbol

In English, primitive is a noun, and it means, "an original or primary word; a word not derived from another, as opposed to derivative". In JavaScript, primitives have no methods and are immutable, meaning they cannot be changed. Furthermore, we also have object, which is a non-primitive built-in type.

When thinking of types, most of us probably think of a strongly typed language such as Java or C++, where we would declare a variable with the type int or string like:

int x = 1;
string y = "hello";

But that's not how we do it in JavaScript. In JavaScript, our variables themselves do not have types, but our values do. This allows us to store different types of values with the same variable. We may use the typeof operator to inspect which type a value has:

// new value with a different type
var x = 1;
x = "hello";

// typeof
typeof "I'm a string";  // string
typeof 1;               // number
typeof true;            // boolean
typeof undefined;       // undefined
typeof null;            // object (this is a very old bug...)
typeof {};              // object

Object Wrapper

Now, you've probably done something like this before:

var myString = "hello world";
myString.length;

But, let's ask ourselves, how is this actually possible? Because when you think about it, it shouldn't work because the primitive value "hello world" does not have a property length (remember, primitives do not have methods).

Well, this is where boxing/wrapping comes in. JavaScript, in this case, will coerce the primitive string value to an object by simply doing new String(myString) and then call .length from that object. The newly created object would later be abandoned after calling the method. These kinds of objects are what we refer to as wrapper objects.

The values null and undefined does not have wrapper objects, and therefore, we cannot do null.property or undefined.property. However, let's investigate a string created with the keyword new:

var s = new String("hello");
Object.getPrototypeOf(s) === String.prototype; // true

Yes, this might be pretty obvious. But it makes the point clear that with s we can access all the methods that String.prototype has. So, why not just use new String(), new Number() or new Boolean()? Well, most information out there vaguely says that it's rarely useful, necessary or that there is anything to gain from it. It's better to just allow JavaScript to wrap the values when needed.

Type Coercion in JavaScript

Coercion can be explicit or implicit. Explicit coercion is when it's very clear that coercion is taking place, for example, coercing 1 to "1", would be done with String(1). Implicit coercion, on the other hand, is not that obvious, because it's often done when we use operators with our values. Continuing our example with coercing 1 to "1", an implicit coercion would be 1 + "".

Boolean("hello")      // explicit
if ("hello") { ... }  // implicit

String(10)            // explicit
10 + ''               // implicit

Number("2")           // explicit
"2" < 1               // implicit     

There are certain rules for how different types of values get coerced. Luckily the EcmaScript Specification has written them down for us. In this section, we'll look at how different primitive value types can get conversed to strings, booleans, and numbers.

1. ToBoolean (argument)

// 1. Undefined or Null: return false
// 2. Boolean: return argument
// 3. Number: if argument is +0, -0, or NaN, return false; otherwise return true
// 4. String: if the argument is the empty String (its length is zero), 
// return false; otherwise return true.

Boolean(undefined); // false
Boolean(null);      // false
Boolean(true);      // true
Boolean(false);     // false
Boolean(0);         // false
Boolean(+0);        // false
Boolean(-0);        // false
Boolean(""/0);      // false
Boolean(1);         // true
Boolean(11);        // true
Boolean(22);        // true
Boolean(33);        // true
Boolean("");        // false
Boolean("hello");   // true

2. ToString (argument)

// 1. Undefined: return "undefined"
// 2. Null: return "null"
// 3. Boolean: "true" if true, "false" if false
// 4. Number: (see section 7.1.12.1)
// 5. String: return argument

String(undefined); // "undefined"
String(null);      // "null"
String(true);      // "true"
String(false);     // "false"
String(1);         // "1"
String(-1);        // "-1"
String(-0);        // "0"
String(+0);        // "0"
String(0);         // "0"
String("hello");   // "hello"

3. ToNumber (argument)

// 1. Undefined: return NaN
// 2. Null: return +0
// 3. Boolean: if true, return 1. If false, return +0
// 4. Number: return argument
// 5. String: (see section 7.1.3.1)

Number(undefined); // NaN
Number(null);      // 0
Number(true);      // 1
Number(false);     // 0
Number(-4);        // -4
Number(11);        // 11
Number(33);        // 33
Number("2");       // 2
Number("hello");   // NaN

Implicit Coercion with Operators

We briefly covered ToBoolean, ToString, and ToNumber which showed us the result of trying to coerce a certain value type to another value type. But implicit coercion isn't this obvious, so now we will look at how coercion works with the commonly used operators ==, +, -, *, /, and %.

I have written down a simplified version of the rules of coercion with these operators, based on the ECMAScript specification. It only covers the data-types boolean, null, undefined, number, and string.

Equal Operator (==)

Just to be clear, == allows coercion while === does not. Many JS-developers advocate using the strict === to avoid unexcepted behaviour that may occur. Anyway, here are the simplified rules for the equality operator:

  1. If x and y has the same type, return x === y
  2. If x is null and y is undefined, return true
  3. If x is undefined and y is null, return true
  4. If x is number and and y is string, return x == ToNumber(y)
  5. If x is string and and y is number, return ToNumber(x) == y
  6. If x is boolean, return ToNumber(x) == y
  7. If y is boolean, return x == ToNumber(y)
1 == 1             // 1 === 1 results in true
null == undefined  // true
undefined == null  // true

30 == "30"          
// Apply rule 4: 30 == ToNumber("30")
// Apply rule 1: 30 === 30
// 30 === 30

"30" == 30
// Apply rule 5: ToNumber(“30”) == 30
// Apply rule 1: 30 == 30
// 30 === 30

true == 1
// Apply rule 6: ToNumber(true) == 1
// Apply rule 1: 1 === 1

The following can be confusing:

Boolean("30"); // true
true == "30";  // false

How does this make any sense? Well, I won't argue if it makes sense or not, but here's how it works:

true == "30"
// Apply rule 6: ToNumber(true) == "30"
// Apply rule 4: 1 == ToNumber("30")
// Apply rule 1: 1 === 30

Addition Operator (+)

With + we either do numeric addition or string concatenation. The simplified rules are:

  1. If x or y is a string, return concatenation of x and y
  2. If x and y is not a string, return ToNumber(x) + ToNumber(y)
// Examples of rule 1
"hello " + true       // "hello true"
"hello " + null       // "hello null"
"hello " + undefined  // "hello undefined"
"hello " + 123        // "hello 123"

// Examples of rule 2
true + true    // 1 + 1 resulting in 2
false + false  // 0 + 0 resulting in 0

The (-, *, /, %) operators

Simply do, return ToNumber(x) (- OR * OR / OR %) ToNumber(y):

true - true // 0
"20" * 2    // 40
12 / false  // Infinity
3 % "9"     // 3

Further Reading

As we just saw, coercion can be very powerful and useful, but one must master it first. Both implicit and explicit coercion can confuse if the developer is not aware of the rules.

If you want to read more about coercion, then I recommend the following:

  1. ECMAScript 2019 Language Specification (section 7)
  2. Table of JavaScript coercion rules
  3. You Don't Know JS: Types & Grammar