Mistake - comparison with optional chaining
I broke CSSBattleâs leaderboard recently because of an interesting bug. We had a piece of code, whose simplified version looked something like this:
const isMore = a.score > b.score;
This got recently changed so that score was now inside a key called overall
and also, the overall
key was optional - could be there or not. Also, in the old scenario score
was always guaranteed to be there. But in the new structure, because original
key itself isnât guaranteed, hence the score
key too. So very conveniently I changed the above comparison to access score
from overall
key through an optional chaining operator so that I don't get an error if overall
key doesnât exist:
const isMore = a.overall?.score > b.overall?.score;
Everything seemed to be working with the changeâŠuntil I noticed that the comparison was returning faulty results in some cases.
The Bug
Hereâs what happened. Assume in the original code, we have a.score
as 40 and b.score
as 0. With those values, isMore
would come out to be true
- which is correct. But what if now, a.original.score
is 40, but b.orginal
doesnât exist. We expect isMore
to be still true
because 40 is more than nothing, right? But that doesnât happen - isMore
is false
in the new case. Letâs see why.
Putting values in our expression, we get:
const isMore = 40 > undefined; // evaluates to false
Note: right side of the >
operator would evaluate to undefined
because of the optional chaining operator on a key which doesnât exists. And 40
, though we expect to be more than something which doesnât exists, is actually not more than undefined
!
That makes sense too (now at least đ). I say the value âdoesnât existsâ a lot of times above, but undefined
doesnât mean âdoesnât existâ, it means the value isnât defined. In JS, the closest to âdoesnât existsâ would be null
. And as should be expected, 40 > null
actually is true
!
Solution
I patched the above expression to have 0
as default value when original
key doesnât exist:
const isMore = (a.overall?.score || 0) > (b.overall?.score || 0);
Now, we wonât have undefined
ever so numeric comparisons work correctly. Fixed!
Iâll see you again with my next mistake! đđ