Introduction

Everyone loves to hate on JavaScript == operator. Some people will tell you it’s all overblown. I'm here to tell you why it's not and prove that the == operator is bad, actually. I know, very brave.

The big issue is that == performs type conversion, and so do operators like + and *. That means JS will automatically convert data of one type to another in certain circumstances. For example:

 

'2' * '3' === 6
'number ' + 1 === 'number 1'

 

You can see that JS automatically converted the types so that all these operations make sense. Normally, you can’t multiply two strings. So, JS converted them to numbers and then multiplied them. The opposite happened for addition, where it converted the number to a string.

 

The conversion itself is not necessarily an issue. The unpredictability is. The conversion rules are not completely arbitrary, but they are complex and unfamiliar, and most developers would rather spend their time developing than memorizing them. To illustrate this point, here are a few examples of expressions that are true in JavaScript.

 

1 == '1'
0 == ''
0 == ' '
0 == '\n\n0\t ' // that's a lot of characters...
0 != 'a'

[1] == '1'
[1,2] == '1,2'
[1,2] != [1,2]
[1,2] != '[1,2]'

[] == 0
![] == 0 // [] and ![] are both 0?
!![] != 0

[] == ![] // That's... weird
[] != []
![] == ![]
[] == ''
[]+[] == ''

[0] == 0 // This almost makes sense
[0] == false

![0] == false
[0] != [0]
[[[[]]]] == 0
[[0]] == 0
[[0]] == '' * 0 + false
[[0,0]] != 0
[[1],[[2]]] == '1,2' // That's a lot of brackets

''*false+0-'b' != ''*false+0-'b' // Both sides are the same
''*false+0-'b'+'a' == ''*false+0-'b'+'a' // That's just the above with 'a' added on the end

 

Background/Problem

As you can see with just a few examples, there are a lot of unexpected results for such a seemingly simple operator. Since you don’t specify types in JS, it is very easy to accidentally fall into a == trap. Sure, 1 == ‘1’ makes enough sense, but [1] == ‘1’? [] == 0? What’s going on here? Let’s find out.

 

We’ll go through the examples grouped by behavior. (Note: every time == or != is used in this post they return true unless otherwise indicated. Remember that.) Here is the first group:

 

1 == '1'
0 == ''
0 == ' '
0 == '\n\n0\t '
0 != 'a'

 

What's up with these? Well, it would help to know the rules in JS for comparing numbers to strings, since that’s what’s happening in all of these examples. The first example, 1 == '1', is pretty obvious. Clearly, 1 is converted to '1' or the other way around, so they're equal. But what about the next two?

 

A reasonable guess is that the string is converted to a number, and then the numbers are compared. If we assume that the number conversion is pretty generous, then these comparisons all make sense. Let's try that.

 

0 == '' // true
0 == parseInt('') // false

 

Huh? Well, what does parseInt return when it can't parse the string? NaN (Not a Number). NaN is not equal to anything, including itself. parseInt on the empty string is NaN. The "canonical" way to convert a string to a number is actually to use the Number constructor (see here), which ignores leading and trailing whitespace, and returns NaN if the string is not a valid number.

 

0 == ''
Number('') === 0
0 == ' '
Number(' ') === 0
0 == '\n\n0\t '
Number('\n\n0\t ') === 0 // \n (newline) and \t (tab) are both whitespace characters.
0 != 'a'
isNaN(Number('a')) === true // 0 != NaN

 

How about a few more cases?

 

[1] == '1'
[1,2] == '1,2'
[1,2] != '[1,2]'

[1,2] != [1,2]

 

In JavaScript, when comparing arrays to primitives the array’s elements are converted to strings by joining them with commas. So the first three cases above become

 

'1' == '1'
'1,2' == '1,2'
'1,2'!='[1,2]'

 

The last case is different. [1,2] != [1,2] because the arrays are objects. Two objects are compared as equal only if they are the same object. This is true for all objects, not just arrays. The arrays in this example are two separate objects, they just happen to have the same content. Here are some more examples to illustrate this point:

 

x = [1,2]
y = x
x == x // true
x == y // true
x != [1,2] // true
x[1] = 3 // [1,3]
x == y // true

 

Regular objects work the same way:

z = {}
z == z
z != {}

 

Speaking of objects, how about null and undefined? Null and undefined mean different things, and are actually different types. undefined means a variable does not have a value whereas null means a variable has no value. You can read more about that here. Here are a few examples of how they compare to each other and other JS values:

 

null == undefined
null != false
null != 0
null != 'null'

 

Useful feature alert!!! Using ==, null and undefined are not equal to anything except each other. This is occasionally useful and is easy to reason about.

 

function do_something(x) {
  if (x != null) {
    // x is not null or undefined
    console.log(x)
  }
  // x is null or undefined
  console.log("nullish")
}
do_something(null) // nullish
do_something(undefined) // nullish
do_something(0) // 0

 

This is pretty much the only usable “feature” of == in JavaScript. Even then, you have to be confident other developers know this behavior and there are often better ways to do what you want.

 

Now let's move on from the simple null/undefined case and get a little weird.

 

[] == 0
![] == 0
!![] != 0

 

From the top:

  • The first line makes sense, but maybe not for the reason you would expect. [] == 0 because [] is converted to a string. Joining all of its elements with commas gives '', which is converted to a number, which is 0.
  • ![] == 0 because ![] is actually false, because [] is true. false is converted to a number, 0, which is equal to 0.
  • !![] != 0 because !![] is actually true, because ![] is false, because [] is truthy. Side note: you can use !! to convert something to a boolean! It might look like an operator, but it is literally just 2 ‘not’s. You can read it as !(![]). A double negative makes a positive! Anyways, !![] is true. true is converted to a number, 1, which is not equal to 0.

Arrays can behave really unintuitively because an empty array is truthy, but empty arrays are sometimes converted to empty strings, which are falsy. As usual, all of the following are true:

 

[] == ![]
[] != []
![] == ![]
[] == ''
[]+[] == ''

 

Lightning round!

  • [] == ![]? That's pretty weird. Well, ![] is false, which is converted to a number, 0. [] is converted to a string, '', which is converted to a number, 0. 0 == 0, so [] == ![].
  • [] != [] we've been over. An array is an object and objects are only equal to themselves. Each [] is a new empty array object.
  • ![] == ![] is true because ![] is false. false == false.
  • [] == '' is true because [] is converted to a string, ''. '' == ''.
  • []+[] == '' is true because []+[] actually is ''. The []s get converted to strings by the + operator.

Now things are starting to make sense!

 

So what happens when the arrays aren’t empty? Here are some examples of that:

 

[0] == 0
[NaN] != NaN
[0] == '0'
[NaN] == 'NaN'

 

These array comparisons all start with converting the array to a string:

[0] == 0
[NaN] != NaN
// becomes
'0' == 0
'NaN' != NaN

 

Then, the string is converted to a number:

 

'0' == 0
'NaN' != NaN
// becomes
0 == 0
NaN != NaN

 

For the string comparisons, the conversion to a string is all that happens.

 

[0] == '0'
[NaN] == 'NaN'
// becomes
'0' == '0'
'NaN' == 'NaN'

 

Time for a whole bunch more:

 

[0] != [0]

[0] == false

![0] == false
[[[[]]]] == 0
[[0]] == 0
[[0]] == '' * 0 + false
[[0,0]] != 0

[[1],[[2]]] == '1,2'

 

There's actually not much new here. Let's go over them quickly

  • [0] != [0] because [0] is an object, and objects are only equal to themselves. Each [0] is a new object.
  • [0] == false because both sides end up converted to numbers in the end. [0] becomes a string '0' which becomes the number 0. false directly becomes the number 0. 0 == 0.
  • ![0] == false seems like it should be the opposite of [0] == false, but it isn’t. ![0] is false because an array is truthy, and ‘not’ on a truthy value is false. false == false.
  • [[[[]]]]==0 because [[[[]]]] is converted to a string. This has to be done recursively because it has to convert each array’s array to a string, which needs to convert its array, and so on. By the end, the string is '', which is converted to 0 as before. 0 == 0.
  • [[0]] == 0 because [[0]] is converted to a string, '0', which is converted to a number, 0. 0 == 0.
  • [[0]] == '' * 0 + false: The behavior of * is new. It converts its arguments to numbers, and then multiplies them. '' is converted to 0. 0 * 0 + false is 0 + false, which is 0. [[0]] == 0 because [[0]] is converted to a string, '0', which is converted to a number, 0. 0 == 0.
  • [[0,0]] != 0 because [[0,0]] is converted to a string, '0,0', which is converted to a number. Since it is not a number we get NaN, and NaN != 0.
  • [[1],[[2]]] == '1,2' because [[1],[[2]]] is recursively converted to a string, '1,2'. '1,2' == '1,2'.

If you can understand all that, then you’re golden. You may even be ready for the final example:

 

''*false+0-'b' != ''*false+0-'b'
''*false+0-'b'+'a' == ''*false+0-'b'+'a'

 

This one's weird to look at, but under the hood, it's pretty simple. The left and right-hand side of both equations are the same, and they're not objects, so it's weird that one is != but the other is ==. Here’s the explanation:

  • ''*false+0-'b' != ''*false+0-'b'. Why? Well, what does the expression evaluate to? ''*false+0 is 0 because ''*false is 0, since '' and false are converted to numbers. The overall expression is then 0-'b'. You can't subtract a string from a number, so the result is NaN. NaN != NaN.
  • ''*false+0-'b'+'a' == ''*false+0-'b'+'a'. Why? This expression is the other expression with 'a' added to the end. We know the first expression is NaN. What is NaN + 'a'? We just convert NaN to a string and concatenate them to get 'NaNa'. 'NaNa' == 'NaNa'.

 

Benefits/Impact

You'll notice that many of these comparisons go through multiple levels of conversion to get to the end result. This is part of what makes the == operator difficult to reason about. The == operator also hides intent. Maybe you, the developer, knew that you were intentionally comparing [] to coerce to '' to coerce to 0 to get true, but someone looking at your code might not. It's better to be explicit about your intent and use === instead. That’s the “strict equality” operator. === does not coerce types, so it's much easier to reason about.

So, what are the rules? Here's a summary, you can read about them in more detail on MDN:

  1. If the types are the same, compare as you'd expect.
  2. null == undefined
  3. Convert objects to primitives, and compare those.
  4. If one is a boolean, convert it to a number and compare.
  5. If one is a string and the other is numerical (number or BigInt), convert the string to be numerical and compare. (Note: NaN != NaN and non-number strings are converted to NaN)

Well, that's actually not so bad! Those rules really aren't that complicated, but they are complicated enough that it's not worth memorizing them. Especially since step 3 (convert objects to primitives) holds a lot of hidden complexity. Maybe if everyone you worked with knew the rules it would be worth it for you to know them too; but they don't, and it isn't. It's better to just use === and !== in almost all cases and forget about the terrors of ==.

 

Solution

If this operator is so terrible, what is there to do? Well, use === of course. But that’s not the end of the story. You should definitely be using code quality tools in your codebase, and a linter will be happy to catch the use of == along with many other bad code patterns. In ESLint, it is the eqeqeq rule. TypeScript will also warn you if you’re trying to compare two types that you probably shouldn’t. The equality operators look pretty similar, so it’s easy to miss them in a code review. But most importantly, at the end of the day, it isn’t really that big of a deal.

 

In most cases using == isn’t catastrophic. It’s not likely to bring down your entire site or result in a company-destroying security vulnerability. You’re probably using it to compare things that are the same type anyways. Just know that if you aren’t, or you think you are but you’re wrong about the types, it can result in some seriously unexpected behavior.

 

Conclusion

The == operator is fraught with potential errors. It was designed to be easy to use, so you could do cool things like 1 == ‘1’. Unfortunately, that made it hard to use because there are a lot of cases where that intuition doesn’t work. Even if your intuition is correct, it’s often still wrong. The reason [] == false isn’t really because they are similar, but because [] as a string is '' which as a number is 0, and false as a number is also 0.

Not every idea pans out, and JavaScript arguably has more than its fair share of bad ideas, but that's okay. Through working with JS, I’ve found it’s adopted a lot of good ideas too. At least the === operator exists, and you can use that. Unfortunately, it’s easy to miss == vs === in a code review, but it’s not the end of the world. Just add a lint rule or use TypeScript.

Untitled design (6)

Author Bio

Ethan Ferguson is an associate software engineer at Jahnel Group, Inc., a custom software development firm based in Schenectady, NY. At Jahnel Group, we're passionate about building amazing software that drives businesses forward. We're not just a company - we're a community of rockstar developers who love what we do. From the moment you walk through our door, you'll feel like part of the family. To learn more about Jahnel Group's services, visit jahnelgroup.com or contact Jon Keller at jkeller@jahnelgroup.com

Unlock Your Future!

Send us a message