JavaScript Coercion : Beyond Basics
Tags: javascript, programming, webdev
Published: 2023-12-02 15:03:14

In JavaScript, we often see implicit type conversion in our code which occurs due to abstract operation. In JS, we use the term coercion for what's commonly known as type conversion. When we consider conversion and coercion, it's best to see them as interchangeable, especially in the context of JavaScript.

Coercion is a weird topic in javascript, that is why many tends to ignore this topic. However, we can't ignore something that behaves counterintuitively. We will start by exploring how abstract operations and coercion occur implicitly. Following that, we'll discuss why they are important.

> Here one thing to note that, abstract operations are not like regular functions that you explicitly call in your code. Instead, they represent conceptual operations that help describe the behavior of certain language features.
> 
> While there may be internal methods or mechanisms within the JavaScript engine that handle the tasks associated with abstract operations, the key idea is that these operations are more about defining behavior and functionality at a higher level of abstraction.

## ToPrimitive Operation

The abstract operation that we will talk about first is called `ToPrimitive` or primitive coercion process. The `ToPrimitive` operation converts a value to a primitive value, either a string, number, or a default value. It is often invoked implicitly by JavaScript when an object is used in a context where a primitive value is expected.

Let's say we are performing an operation where it requires a value to be primitive. Now, if we don't have a primitive in there, we need to turn that into a primitive.

If we have a non-primitive entity that needs to be transformed into a primitive one, conceptually, what it must follow is a series of algorithmic steps. This set of steps is referred to as `ToPrimitive`, almost as if it were a function that could be called.

The `ToPrimitive` operation takes an optional type `hint`. The hint helps to figure out what it needs to be converted to. Let's say if we are doing a numeric operation and if there needs to run the `ToPrimitive` operation, the hint will be something like this - 'I would like it to be a number'. It's just an implicit process where JavaScript understands what type it needs to be. If we do something string-based, it sends a hint as a string. These are the two basic hints. And if it can't figure out the hint, it returns whatever the best primitive value it can return in that context.

**One important thing is that -**

> JavaScript algorithms inherently follow a recursive pattern. For example, if what returns from `ToPrimitive` isn't a primitive type value but instead another non-primitive thing, `ToPrimitive` will be called again. It will keep getting invoked until we obtain an actual primitive. If it can't return a primitive value, it eventually results in an error.

The `ToPrimitive` operation relies on two methods - `valueOf` and `toString`. These can be available on any non-primitive value in JavaScript, be it an object, function, array, or any other non-primitive type. These two methods/functions play a crucial role in the process of converting non-primitive values to primitive ones.

Remember, `ToPrimitive` operation takes an optional hint.

Now if the hint is number, then `ToPrimitive` invokes the `valueOf` method first and see what it returns. If `valueOf` method gives a primitive, then we're done. However, if it doesn't give a primitive or if the method itself doesn't exist, then it tries the second method - `toString`.

If `toString` method also fails to give a primitive, it usually leads to an error.

> JavaScript calls the `toString` method to convert an non primitive to a primitive value. We rarely need to invoke the `toString` method ourselves; JavaScript automatically invokes it when encountering an object where a primitive value is expected. [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString)

If the hint is string, still both `valueOf` and `toString` method occurs, but in reverse order. In terms of string hint, if it `ToPrimitive` operation tries to make a non primitive into a primitive, `toString` method invoke first. Then, if it gives us a primitive like a string, we will just use that.

So, no matter what the hint is, if we are trying to use something that is not primitive where it needs to be primitive, like in some math or concatenation, then it goes through `ToPrimitive` algorithm and it ends up either invoking the `valueOf` method or `toString` method.

## Diving deep into valueOf method

Let's take an object - `const obj = {value: 4}`.

Now, `obj + 3` returns us `'[object Object]3'` as output. But, shouldn't it be 7? Since the object's value is giving a number hint, it should already be turned into a number, right? Then, why is it behaving this way?

To figure out that answer, we need to understand that, under the hood, JavaScript is still running the `ToPrimitive` operation on our written `obj` to convert it to work in primitive operations - like adding value. And, since it's taking a number as a hint - as we know, it is running the `valueOf` method first. But, the problem is, the default `valueOf` method of JavaScript, which gets inherited from `Object.prototype`, is basically useless in this case because the `valueOf` method inherited from `Object.prototype` returns the object itself (`this`).

And, since the `ToPrimitive` algorithm is recursive, it runs again, and sees that the `valueOf` method is not working, so, it falls back into the `toString` method. And when the `toString` method runs on any object type, it returns - `"[object Object]"`

So, our object basically is turning into a string first which is `"[object Object]"`, Then, when we use the `+` operator between two `string` types, it just concatenates them.

```js
obj + 3

"[object Object]" + 3

"[object Object]3"
```

Many built-in objects override the `valueOf` method to return an appropriate primitive value. When you create a custom object, you can override `valueOf` to call a custom method so that your custom object can be converted to a primitive value when needed.

For example -

```js
const obj = {
	value: 4,
}

obj.valueOf() // {value: 4}
```

It is returning the object itself(`this`). But, you can create a function to be called in place of the default `valueOf` method. Your function should take no arguments, since it won't be passed any when called during type conversion.

```js
const obj = {
    value: 4,
    valueOf: function() {
        return this.value;
    }
};

obj.valueOf() // 4
```

In this case, it is just returning the value. And, if we try to execute something where our written `obj` will need to be a primitive type, we will see that it will behave as expected -

`obj + 3` will return 7.

Why? Because, since the adding operation is invoking the `ToPrimitive` operation with a `number` hint, so, the `valueOf` method is running first. But, now, our `obj` has its own custom `valueOf` method, so it's not executing the inherited `valueOf` method from `Object.prototype` which returns `this` object itself. Instead, for our custom `valueOf` method, it's giving us the actual number form of the value, since we are getting a primitive type already, it's not falling into the `toString` method.

## ToString Operation

Here are some values and their corresponding string values after performing the `ToString` operation on them:

- `null` => "null"
- `undefined` => "undefined"
- `true` => "true"
- `false` => "false"
- `3.14` => "3.14"
- `0` => "0"
- `-0` => "0"  

The `ToString` operation is executed during abstract operations involving implicit type conversion.

When `ToString` is applied to object types like arrays, it returns values as demonstrated in the following examples:

- `[]` => ""
- `[1, 2, 3]` => "1,2,3"
- `[null, undefined]` => ","
- `[ [ [ ], [ ] ], [ ] ]` => ",,"
- `[,,,]` => ",,,"

**For objects:**

- `{}` => "[object Object]"
- `{a: 2}` => "[object Object]"

We can modify the default behavior of `toString` by implementing a custom `toString` method in our object. It will be called when needed:

```javascript
{toString() { return "A"; }} => "A"
```

## ToNumber Operation

There are a lot of corner cases involved in terms of ToNumber.

If we need to do something numeric and we don't have a number, JS invokes the ToNumber abstract operation. Some conversions are pretty straightforward, while others are counterintuitive.

- `"" => 0`
    
    - An empty string returns 0. Empty string should be NaN, right? Empty means there is nothing. How can nothing be 0? That's weird. It's not only a weird case in JavaScript; it creates a lot of other problems in terms of [[Coercion]] in JS.
    
- `" 003 " => 3`
    - If there are any empty spaces, JS removes those and returns the corresponding number.


**Others are pretty straightforward:**

- `"0" => 0`
- `"-0" => -0`
- `"3.14" => 3.14`
- `"0.0" => 0`
- `"." => NaN`
    - "." turns into NaN, which is as it should.
        
- `"0xaf" => 175`
    - Hexadecimal string turns into their corresponding numbers.
        
- `false => 0`
- `true => 1`
    - Within the context of programming, maybe it's a sensible thing to turn false and true into 0 and 1, as that's how computers treat them. But, it creates an issue. false and true should turn into NaN. We will see the reason later when we discuss the corner cases.
        
- `null => 0`
- `undefined => NaN`
    - Null and undefined both should be NaN, but one turns into 0, and another turns into NaN. That's interesting. For those who think that converting null uses the valueOf method of Object because null is an object, one interesting thing is that null doesn't inherit methods from Object.prototype.


### ToNumber Operation on Non-Primitive

When we run ToNumber on a non-primitive or object, it evokes the `ToPrimitive` operation with the number hint. In `ToPrimitive` operation, it first runs the `valueOf` method, which is inherited from the Object.prototype by default, and it returns the object itself (`this`). JS ignores that, and it falls into the `toString` method.

Since it falls into `tpString`, no matter if our hint is a number or not, the value turns into a string. So, we can think of the numberification of an object as the stringification of it.

There are cases where we want something to be a primitive number, and we can end up with a primitive string for the internal process of `valueOf`. We need to be aware of that.

If it's empty like `[]` or `{}`, then the same things happen. It evokes the `ToPrimitive`, and the `valueOf` returns the empty parenthesis - `[]` or `{}`, then it falls into `toString` as usual.

**Some more ToNumber operations in terms of arrays:**

- `[""]` => 0
- `["0"]` => 0
- `["-0"]` => -0
- `[null]` => 0
- `[undefined]` => 0
- `[1,2,3]` => NaN
- `[[[]]]` => 0

**Few important things to notice here:**

- `[""]` falls into ToString, and then it returns an empty string. If `ToNumber` tries to convert an empty string, it converts it to 0, which is our old problem as we saw earlier.
    
- Similar things happen for `[[[]]]` - first, it turns into "" by toString, then, it converts into a number, which is 0. Emptiness shouldn't be 0 in this case.
    
- We see that `[null]` and `[undefined]` return 0 in the ToNumber operation, which doesn't make sense. We have seen different behavior in their case when we have tried to convert primitive null and undefined. But, in this case, they are returning an identical value, which is 0. Why? In this case, this is a non-primitive thing in both cases, and as it's non-primitive, `ToPrimitive` evokes, and the valueOf method runs first. It returns `this`, then it falls into the `toString` method, and remember what `toString` does on a non-primitive with null or undefined? It turns into an empty string. And then, that empty string turns into 0. Our root problem again.
    
- `{..}` => `NaN`
    - If it's an object {}, then the same thing happens. `ToPrimitive` evokes, then it falls into toString. ToString returns "[object Object]", which is definitely not a representation of a number, so it returns `NaN`.
        
    - But, if we add our custom `valueOf` method, it doesn't fall into toString, and it returns what we want to be returned.


## ToBoolean Operation

This is pretty straightforward. We just need to remember all the falsy values, which will always return false if we try to run ToBoolean operation on them. Everything else returns true.

**Falsy values**: "", 0, -0, null, NaN, false, undefined **Truthy values**: Everything else.

One thing is important to notice here:

> When we try to convert anything to Boolean, it doesn't evoke any other operation under the hood, like `ToNumber`. It just checks its truthiness or falsiness.

That's why if we convert [ ] to a boolean, it returns true, and if we convert "" to a boolean, it returns false. Because an empty string is a falsy value, [ ] is not.

If it would evoke the `toString` method under the hood, that would convert [] to an empty string, and then we would get false because of that empty string. But, in this case, that is not happening.

## Corner Cases

Every language has type conversion. We can't just avoid coercion in JavaScript for its corner cases. No language is free from various kinds of corner cases. We just need to be aware of those corner cases and learn how to work around them. Here are some of the corner cases we need to be aware of -

```javascript
Number("") // 0  (!!!)
Number("   \t\n") // 0  (!!!)
Number(null) // 0  (!!!) should be NaN!
Number(undefined) // 0 
Number([]) // 0
Number([1,2,3]) // NaN


Number([null]) // 0  -> !!!
Number([undefined]) // 0  -> !!!

Number({})  // NaN


String(-0)  // "0"   -> what! should be "-0"!
String(null)  // "null"  -> good!
String(undefined)  // "undefined"  -> good!
String([null])  // ""  -> !!!
String([undefined])  // ""  -> !!!

Boolean(new Boolean(false)); // true  -> what!!!
```

One of the root causes of corner cases occurring in coercion in JS is the conversion of "" to 0. We have already seen what kind of problems it can cause. Many coersions would just be gone if an empty string wouldn't turn into 0 in number conversion; if it would be NaN. Whatever, we can't do anything about it now because of backward compatibility.

Another important corner case is true becoming 1, and false becoming 0 in terms of converting to number operation. `Number(true)` -> 1 `Number(false)` -> 0

Though it is an intuitive thing to us. Let's see how it creates a problem. `1 < 2` -> true; Okay. `2 < 3` -> true; It's also okay. `1 < 2 < 3` -> true. Okay. You are thinking - it's exactly working as it should be.

But that is not the case; what is happening under the hood is it first returns `true` for the first part. Then, true converts into 1, since we are performing a numeric operation. Then, `1 < 3` obviously returns true.

```javascript
(1 < 2) < 3
(true) < 3
1 < 3
```

We see how this can create a problem.

```javascript
7 > 5 // true

5 > 3 // true

7 > 5 > 3 // false --> !!!
```

Let's see, which is actually happening.

```javascript
(7 > 5) > 3
(true) > 3
1 > 3 // false
```

Since 1 is not greater than 3, it returns false. Now, we understand how converting `true` and `false` to 1 and 0 creates problems. It would be solved if it were just NaN.

Some of us hate coercion because of it's weird corner cases. But, we can't ignore coercion. We use coercion all the time.

## Practical Use Cases of Coercion

Coercion is a weird topic in JavaScript. But, we can't avoid it either. It turns out, we all deal with coercion all the time! Let's see some examples.

```javascript
var numPeople = 19;

console.log(`There are ${numPeople} people out there.`);

// There are 19 people out there.
```

```javascript
var firstPart = "There are ";
var numPeople = 19;
var secondPart = " people out there.";

console.log(firstPart + numPeople + secondPart);

// There are 19 people out there.
```

It turns out that if we use the `+` operator and any of the values is a string, it converts the entire expression into a string. This is known as **Operator Overloading**. Our number is implicitly converting into a string. Some may want to make it explicit:

```javascript
var numPeople = 19;

console.log(`There are ${numPeople + ""} people out there.`);

// There are 19 people out there.
```

or

```javascript
var numPeople = 19;

console.log(`There are ${[numPeople].join("")} people out there.`);

// There are 19 people out there.
```

or

```javascript
var numPeople = 19;

console.log(`There are ${numPeople.toString()} people out there.`);

// There are 19 people out there.
```

or

```javascript
var numPeople = 19;

console.log(`There are ${String(numPeople)} people out there.`);

// There are 19 people out there.
```

Writing explicit code is not a good decision always.

Some may ask, what about converting a string to a number? We do that too. What about form data? We all need to work with form data as JavaScript developer.

```javascript
function addOnePeople(numPeople) {
    return numPeople + 1;
}

addOnePeople(peopleInputElement.value); // get people number from form

// "191" - WHAT!!!!
```

So, again, we need to convert it into a number first! There are two ways. First one -

```javascript
function addOnePeople(numPeople) {
    return numPeople + 1;
}

addOnePeople(+peopleInputElement.value);

// 20
```

or we can use the `Number` function, which is the more preferred way.

```javascript
function addOnePeople(numPeople) {
    return numPeople + 1;
}

addOnePeople(Number(peopleInputElement.value));

// 20
```

How about reducing the input value?

```javascript
function ignoreOnePeople(numPeople) {
    return numPeople - 1;
}

ignoreOnePeople(peopleInputElement.value);

// 18
```

Since `numPeople` is a string and we are trying to perform an operation where a number is needed, the `ToPrimitive` process is triggered, and `numPeople` converts into a number automatically. That's why we don't need to explicitly mention that. We even need to check truthy and falsy values all the time.

```javascript
if (peopleInputElement.value) {
    numPeople = Number(peopleInputElement.value);
}
```

Again, if we aren't aware of the corner case of Boolean, we may face a bug here. What if the input element contains a bunch of white spaces? That won't be a falsy value!

## Boxing

We have seen that non-primitive values don't have methods in them. If that's the case, how can we access methods in a string like `somestring.length`?

It's called **Boxing**. It's a form of implicit coercion, although it doesn't occur in the same process as abstract operations do.

It's like when JavaScript sees that you are trying to run a method on a non-primitive value, JavaScript does you a favor. It thinks, "Okay, this person is trying to run an operation as if it's a primitive value (object); let's turn it into that and make their life easier!" And then, it converts it into the corresponding object representation of the string.

Maybe from this, the falsy notion comes: In JavaScript, everything is an object. No, just because it is converting into an object-like structure under the hood doesn't mean it's an object. Things can behave as similar, but that doesn't make them similar.

Two things are pretty different.

A significant part of my motivation to delve deeply into JavaScript was influenced by [Kyle Simpson](https://github.com/getify). I conclude here by sharing some of his philosophy on "coercion."

- You don't deal with corner cases by avoiding coercions.
- You have to adopt a coding style that makes value types plain and obvious.
- A quality JS program embraces coercions, making sure the types involved in every operation are clear. Thus, corner cases are safely managed.
- JavaScript's dynamic typing is not a weakness, it's one of it's strong qualities.
- Implicit doesn't mean magic. It means abstraction. So, that it can hide unnecessary details, re-focusing the reader and increasing clarity.
    - We can simiply depends on implicit type coercion rather than always describing type convertion explicitely every time, which will decrease readability.


It's "Useful", when the reader is focused on what's important. It's "Dangerous", when the reader can't tell what will happen. It's "Better", when the reader understands the code.

It's "Irresponsible" to knowingly avoid usage of a feature that can improve code readability.

And it's always better to learn how things work under the hood!

Happy Learning!

> If you enjoyed reading this, you can connect with me on [**Twitter**](https://thefaisal.dev/twitter) or check out my [**other articles**](https://thefaisal.dev/).

## Additional Resources

[JavaScript Data Structures - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures)  
[valueOf - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)  
[valueOf in JavaScript - ECMA](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.prototype.valueof)  
[Abstract Operations: To Primitive - ECMA](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-toprimitive)  
[You Don't Know JS by Kyle Simpson](https://github.com/getify/You-Dont-Know-JS)