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! đđ