Why Is {} > [] ?
Tags: javascript
Published: 2020-11-24 21:34:40

## [TLDR version](#tldr-version)

## Relational Comparisons

In JavaScript, the result of a relational comparison is determined by the [Abstract Relational Comparison algorithm](https://tc39.es/ecma262/#sec-abstract-relational-comparison).  The algorithm converts both sides of a comparison to primitive values and then returns the result of the comparison between those two primitive values.

## ToPrimitive&sup1;

The Abstract Relational Comparison algorithm calls `ToPrimitive` twice, once for each operand, passing 'number' as the second argument.  This tells the `ToPrimitive` function that if there are multiple primitive types the operand could convert to, and number is one of them, it should convert the value to a number instead of a different type. 

## OrdinaryToPrimitive&sup2;

If the value being passed to `ToPrimitive` is an object, it then calls `OrdinaryToPrimitive` with the same two arguments, value and type hint.  `OrdinaryToPrimitive` generates a list of methods to call to convert the value to a primitive.  

If "string" is passed in as the type hint, the method order becomes `toString` followed by `valueOf`.  In this case, since "number" was passed, the method order is `valueOf` followed by `toString`.  It's important to note that while all values that get to this point are objects, not every value will use the `valueOf` and `toString` methods on the **Object** prototype.

If the first method results in a value of type "object", the result of calling the second method returned.   If the first method does not return a value of type "object", the result of the first method is returned.

## OrdinaryToPrimitive( {} )

In the case of {}, the only prototype being looked at is **Object**, so it first tries calling `valueOf` on the object using `Object.prototype.value()`&sup3;, but that returns {}.  Since typeof {} === "object", it moves to the next method. It then calls `Object.prototype.toString()`&#8308;
;  If `Object.prototype.toString()` is called on a value that is an object, the builtinTag is set to "Object".  The return value of `Object.prototype.toString()` is the concatenation of "[object ", tag, "]".  The return value for passing in an empty object, then, is "[object Object]"

## OrdinaryToPrimitive( [] )

In the case of [], there are two prototypes to take into consideration -- **Array** _and_ **Object**.  If a method exists on the **Array** prototype, that is the method called.  If, however, it does not exist on the **Array prototype**, it looks for the method on the **Object** prototype.  The **Array** prototype does not contain a method for `valueOf`, so it first tries calling `Object.prototype.valueOf()`.  That returns [], and since typeof [] === "object", it moves on to the next method. 

The **Array** prototype does have a `toString()` method, so It then calls `Array.prototype.toString()`&#8309;.

`Array.prototype.toString()` returns the value of the `join` method on the array.  As there are no elements in the array, the return value of `Array.prototype.toString()` on an empty array is an empty string.

## Comparison

Now that both sides are converted to their primitive values, it's time to compare them in relation to each other.  

```javascript
"[object Object]" > ""
```

A string of any length is going to be greater in value than the value of an empty string. 

## Follow-up

The way that JavaScript evaluates abstract equality when one operand is of type String/Number/Symbol/BigInt and the other operand is an object is to call the same `ToPrimitive` on the object and then check equality&#8310;. 

Therefore, we can also sanity check that {} is actually converted to `"[object Object]"` and [] is converted to an empty string by performing abstract equality checks.

```javascript
console.log({} == "[object Object]") // true
console.log([] == "") // true
```

## Why does {} > [] error in browser?

Shout out to [Martijn Imhoff](https://hashnode.com/@MartijnImhoff) for asking this question.

The way that the specification for JavaScript is written, block statements are evaluated before expressions, so when the interpreter sees curly braces when not in an expression context, it interprets them as a block rather than an object literal.  That's why you get an error when you attempt to run those expressions in the browser.  The way to force the interpreter to see {} as an object literal instead of as a block is to wrap it in parentheses.

![({}) > [] // true; ({}) < [] // false; ({}) == "[object Object]" // true](https://github.com/AmyShackles/AmyShackles/blob/master/parens-with-%7B%7D%3E%5B%5D.png?raw=true)

If you were to open a Node console rather than a browser console, you would see:

![{} > [] // true; > {} < [] // false; {} == "[object Object]" // true](https://github.com/AmyShackles/AmyShackles/blob/master/%7B%7D%20%3E%20%5B%5D.png?raw=true)

This is because Node made a change to evaluate input as expressions before evaluating them as statements.  That change can be seen [here](https://github.com/nodejs/node/blob/651088c3e6b6399a3e656e397c3845b970ad7903/lib/repl.js#L355-L358).

## TLDR Version

`{}` is converted to `"[object Object]"`

`[]` is converted to `""`

`"[object Object]" > ""`

<hr/>

References:

&sup1; [ToPrimitive specification](https://tc39.es/ecma262/#sec-toprimitive)

&sup2; [OrdinaryToPrimitive specification](https://tc39.es/ecma262/#sec-ordinarytoprimitive)

&sup3; [Object.prototype.valueOf() specification](https://tc39.es/ecma262/#sec-object.prototype.valueof)

&#8308; [Object.prototype.toString() specification](https://tc39.es/ecma262/#sec-object.prototype.tostring)

&#8309; [Array.prototype.toString() specification](https://tc39.es/ecma262/#sec-array.prototype.tostring)

&#8310; [Abstract Equality Comparison algorithm](https://tc39.es/ecma262/#sec-abstract-equality-comparison)