JavaScript sort() not working
JavaScript has a sort()
method that you can use on arrays, but the results are almost always weird and don’t return what you initially expect.
For example, if you have the following [9, 8, 12, 1, 33, 21]
array, using sort()
on it will return [1, 12, 21, 33, 8, 9]
.
On the surface, this makes no sense, and that’s because JavaScript’s sort()
method isn’t exactly what it makes itself out to be. The array returned is hardly sorted at all — or maybe it is, but just not in the way we want it to be.
This is because JavaScript’s sort()
method converts each item in the array into strings and constructs the sequence by comparing each array item based on the UTF-16 code values when there is no callback specified.
UTF what?
I know. Welcome to the seemingly weird but highly logical parts of JavaScript.
How UTF-16 Works
JavaScript is not a typed language — as much as many of us want it to be. When sort()
is used, it automatically calls the String()
cast method on every array element by default and turns everything into a string. It does this in order to ensure that things can be sorted in a uniform manner. It’s easier to sort when things are the same type.
UTF-16 stands for 16-bit Unicode Transformation Format. It’s the standardized form for translating bits into a format comprehensible to humans. It’s basically a translation table for a corresponding character.
When you use JavaScript’s sort()
, you are essentially sorting against whatever order the characters are in for this table. That is why 33
came before 8
in the sorted array above. It is because the character 3
appears before 8
in the table. The sort is done based on character appearance rather than mathematical reasoning or rules.
When this logic is applied, JavaScript’s sort()
method isn’t wrong — it’s merely misunderstood.
How to Turn Numbers Into Numbers
When sort()
is used on its own, it returns values based on the order of things in the UTF-16 table. However, sort()
also takes a callback function that allows you to decide how things will appear.
On a technicality, it’s more a comparison function that helps your JavaScript code determine the correct order of things in the manner you want.
This callback function takes two arguments — a
and b
— for convention purposes. You use these arguments to create an equation that either returns 1, -1, or 0.
When the equation returns 1, a
gets to proceed. If the equation returns -1, then b
gets to proceed. If the equation equates to 0, then it means that both are equal in value and it is the end of the recursive sorting algorithm sort()
runs through.
So when you’re working with numbers and using the callback function, the String()
cast is not placed on the array items. This is because sort()
needs a callback comparator, and when sort()
is used without one, String()
acts as the default callback.
Let’s look at the code below:
function sortNumbers(a, b) {
if (a > b) {
return 1;
} else if (b > a) {
return -1;
} else {
return 0;
}
}
This is our callback function that will help sort the numbers in the correct and ascending order. So when you want to sort an array of numbers, you can do so by doing something like this: arrayNameHere.sort(sortNumbers);
.
To inverse it, you can just inverse the logic of the code so it’s b > a
and b < a
.
What About (a, b) => a- b ?
In many tutorials and StackOverflow answers, you may have seen solutions that look something like this: arrayNameHere.sort((a, b) => a - b);
.
The thing with 1 and -1 is that it doesn’t have to be these two numbers specifically. It just needs to fall into one of the two spectrums of either being a positive or negative number. sort()
will still treat it the same way as the code example above.
0 is a definite number that signifies the end of the sorting recursion, so this is the only non-negotiable value.
This is why (a, b) => a - b
works. For example, if a = 5
and b = 2
, a - b
results in a positive number, making a
the bigger number and returning the equivalent of 1.
It is good to note that this kind of sort logic only works with numeric types or objects that return numeric values when valueOf()
is used.
So algorithmically, for an array with three values, it looks something like this:
What About Non-Numeric Sorting Like Months?
For non-numerical sorting, you’ll need to get creative and think of a way to transform it into a numerical comparison in your callback function.
Remember that sort works on positive and negative numbers to determine the positioning of things. So when it comes to months, you can do something like this:
var someItems = ["Feb", "Jan", "Apr", "Dec", "Oct"];
function sortMonths(a, b){
var correctMonthsOrder = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
return correctMonthsOrder.indexOf(a) - correctMonthsOrder.indexOf(b);
}
someItems.sort(sortMonths);
In the code above, we created a list that allows us to attach a numerical value, enabling us to create an order to the final results we want to emulate. By using indexOf()
, we are able to create the returned values required by sort()
in order to figure out the correct sequence of things.
Final Words
I hope this cleared up some of the mysteries behind JavaScript’s nifty sort()
method.
sort()
can be quite powerful when you understand how to use it. A lot of other examples tend to put their callback logic inside sort()
itself, but my personal preference is to make it a separate function so that it’s easier to understand and modularizes your code by creating reusability.
Imagine having to write the code inside sortMonths
over and over again in multiple places. It can get rather cumbersome. Or perhaps the logic gets complicated and you start to lose your brackets and braces in the process. (a, b) => a - b
might look short in the short term, but when the logic expands, things can get messy visually very quickly. This is just a side pointer to help make your code easier to work with for the long haul.
All in all, this is basically how sort()
works in a nutshell.