Frontend Development 10 min read

Why Does [] == ![] Evaluate to True? Unraveling JavaScript Type Conversion

This article explains JavaScript’s explicit and implicit type conversion mechanisms—including Number(), String(), Boolean(), toString and valueOf—through detailed code examples and clarifies puzzling expressions like [] == ![] and {} == !{}.

KooFE Frontend Team
KooFE Frontend Team
KooFE Frontend Team
Why Does [] == ![] Evaluate to True? Unraveling JavaScript Type Conversion

Forced Type Conversion

Number()

Primitive arguments

<code>Number(123) // 123
Number('123') // 123
Number('123abc') // NaN
Number('') // 0
Number(true) // 1
Number(false) // 0
Number(undefined) // NaN
Number(null) // 0</code>

Number() is stricter than

parseInt

. When the string cannot be fully parsed, it returns

NaN

:

<code>parseInt('32 abc') // 32
parseInt('0x11 abc') // 17
Number('32 abc') // NaN
Number('0x11 abc') // NaN</code>

Object arguments

<code>Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5</code>

When an object provides both

valueOf

and

toString

,

Number()

follows a specific order:

<code>const obj = {
  toString() { return 1; },
  valueOf() { return 2; }
};
console.log(Number(obj)); // 2
</code>

If

valueOf

returns an object,

toString

is used instead:

<code>const obj = {
  toString() { return 1; },
  valueOf() { return {}; }
};
console.log(Number(obj)); // 1
</code>

When both methods return objects, a

TypeError: Cannot convert object to primitive value

is thrown.

<code>const obj = {
  valueOf() { return {}; }
};
console.log(Number(obj)); // NaN (no error because only valueOf is present)
</code>

For an empty object, the default

Object.prototype.toString

is invoked:

<code>// Number({})
Object.prototype.toString.call({}) // "[object Object]"
Number("[object Object]") // NaN
</code>

For an empty array,

Array.prototype.toString

yields an empty string, which converts to

0

:

<code>// Number([])
Array.prototype.toString.call([]) // ""
Number('') // 0
</code>

String()

Primitive arguments

<code>String(123) // "123"
String('abc') // "abc"
String(true) // "true"
String(undefined) // "undefined"
String(null) // "null"
</code>

Object arguments

<code>String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"
</code>

When an object defines both

toString

and

valueOf

,

String()

prefers

toString

first:

<code>const obj = {
  toString() { return 1; },
  valueOf() { return 2; }
};
console.log(String(obj)); // "1"
</code>

Boolean()

Falsy values ("falsey")

<code>undefined
null
0 // +0 and -0
NaN
false
''
</code>

All other values are truthy:

<code>Boolean([]) // true
Boolean({}) // true
Boolean(new Error()) // true
Boolean(Symbol()) // true
</code>

Implicit (Automatic) Type Conversion

Automatic conversion to Boolean

Control‑flow keywords and logical operators coerce their operands to Boolean.

Automatic conversion to String

When the

+

operator involves a string, the other operand is converted to a string.

<code>'5' + 1 // "51"
'5' + true // "5true"
'5' + {} // "5[object Object]"
'5' + [] // "5"
</code>

Automatic conversion to Number

All arithmetic operators except

+

(when used for concatenation) coerce operands to numbers.

<code>'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
null + 1 // 1
undefined + 1 // NaN
</code>

Relational operators (

==

,

!=

,

===

, etc.) also perform numeric conversion when needed.

<code>1 == true // true
'1' == true // true
'1' == 1 // true
[1] == 1 // true
[] == false // true
[] == null // false
</code>

Special Values

Infinity

<code>Number(Infinity) // Infinity
1 / 0 // Infinity
1 / Infinity // 0
Infinity === Infinity // true
Infinity === -Infinity // false
0 === -0 // true
0 / 0 // NaN
</code>

NaN

<code>NaN == NaN // false
</code>

undefined and null

<code>null === null // true
undefined === undefined // true
undefined == null // true
undefined === null // false
</code>

Summary

Now we answer the initial puzzles.

[] == ![]

evaluates to

true

because

[]

converts to an empty string

''

, which becomes

0

;

![]

is

false

, which also becomes

0

, so

0 == 0

is

true

.

<code>// [] == ![]
[].toString() // ''
Number('') // 0
![] // false
Number(false) // 0
0 == 0 // true
</code>
{} == !{}

evaluates to

false

because

{}

converts to

"[object Object]"

, which becomes

NaN

;

!{}

is

false

(=> 0), and

NaN != 0

.

<code>// {} == !{}
({}).toString() // "[object Object]"
Number('[object Object]') // NaN
!{} // false
Number(false) // 0
NaN != 0 // false overall
</code>

Further examples such as

{} + {}

,

[] + {}

, and

{} + []

produce string concatenations according to the same conversion rules; you can experiment at jsisweird.com .

JavaScriptString()Boolean()type conversiontoStringvalueOfimplicit conversionNumber
KooFE Frontend Team
Written by

KooFE Frontend Team

Follow the latest frontend updates

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.