The Principle Of Least Astonishment 😲 and JavaScript’s .sort()

Topher Winward
Jun 14, 2018 · 7 min read

When we write code, we should take a moment to consider, “If someone else was reading this for the first time, what would they expect?”

Take the following piece of code — please take a moment to really consider, what you expect the output to be before you run it. I ask this, even if you are not a JavaScript developer.

Available at https://repl.it/@Winwardo/AstonishingJavascript

Despite being a seemingly simple piece of code — only 4 lines, 3 function calls, and only one “weird” bit of syntax on line 4, there is a large amount going on in this snippet.

I asked a few colleagues, from junior to senior levels, with varying experience in either JavaScript or C++ (from 1 year to 10+), and all were tripped up in a different way by this snippet.

🔍 Before running, let’s examine our assumptions

What are some of the possible points of confusion that they encountered here? These are thoughts my colleagues mentioned and expected before running the code — not what the actual behaviour is.

  • “You used to declare , yet you clearly mutate it on line 4.”
  • makes a copy of the array and adds the value to the end.”
  • makes a copy of the array and adds the value to the beginning.”
  • “If is mutating in place, it wouldn’t return anything, definitely not an array.”
  • “If returns an array, it must return a new copy, right?”
  • “How does work? 18 is clearly larger than 2! It must be broken.”
  • “If were sorting by strings, then I would expect “18” to be larger than “2” as it has more characters in it.”
  • “Mutating on line 4 should not also mutate .”
  • , and are all the same array.”
  • would be the value we pushed, 9.”

👟 Running the code

Running the code, you’ll see the following output:

Is this surprising to you?

Let’s break this down in several places. This result doesn’t come from the fact it’s particularly complex code, or that any one line is specifically confusing. It comes from a combination of many, seemingly ambiguous or not entirely clear interfaces working together to confuse readers.

It’s a great example of how the Principle Of Least Astonishment is important to keep in mind when designing libraries, languages and systems to be consumed by other people.

1. ️Let’s start with how .sort() and JavaScript arrays work

JavaScript arrays can be mildly confusing to those who come from strongly typed languages like C++ or Java.

In C++, your array must be of a specific type, say , and trying to place a into it will cause a compile time error. JavaScript has no such qualms: The following is a valid array:

Note how here contains a number, a string, an object, another array, and even a function! How would you possibly sort this array?

Well, let’s call on it and find out! The result is this:

No, this was not randomly jumbled together — it’s all to do with the comparison function. See, actually takes a parameter on how to compare two values. It’s just that if none is given, the default comparison function looks somewhat like this:

By default, JavaScript’s sort converts values to strings before sorting.

This might seem odd — if we have an array full of numbers, why convert to a string before comparing? The answer is simple — the JS engine doesn’t know that it’s an array of numbers. The array could have any value in it, so converting to string first is the simplest solution.

Functions are converted to strings as if their original JS code was a string — so the function in our array would convert to the following string:

Arrays within the array will receive similar but not exactly the same treatment. They will be flattened, then square brackets will be removed. As such:

Objects act in a different way still:

I won’t go any further into why this is or other implications of this conversion to string here, but the key takeaway is that everything in JavaScript can be converted to a string, so any array can be sorted.

This explains why 18 is considered smaller than 2: the string is considered smaller than the string . The comparison will check character by character, and as , the isn’t even checked.

Luckily it’s very easy to tell an array to sort numerically:

2. Mutation is confusing when not clearly signalled

.sort() mutates the array in place, and then returns it 🤢

This means that after calling , the original variable A has changed to be sorted, but also now that . is not a safe new copy of either — it is literally the same thing.

Converting to strings before sorting is confusing but justified: mutating an object in place and returning it is a cardinal sin. (Fluent builder objects are an exception here.)

Many of my colleagues disagreed on whether sort should return a brand new sorted copy of the array, or sort the array in place and return nothing. None of them expected or suggested it should do both.

I want to make this point again as it’s so important and key to the message of this article:

Please do not simultaneously mutate and return in the same function: it is astonishing and unexpected.

If a developer believes your function will act in one way, and doesn’t actively check it doesn’t act in the other, they will believe their assumption was correct and continue on, potentially causing issues later down the line.

3. JavaScript is pass by value-of-address 📨

Asking whether a language passes by value, pointer or reference is a mind-set that many C, C++, Rust and other developers used to strongly/statically typed languages can have. However, JavaScript, Java, C# and more simply don’t work like that. This StackOverflow answer concisely sums up what JavaScript actually is: https://stackoverflow.com/a/6605700

As such, because , mutating with also (possibly unexpectedly) changes as well.

This is why both and have a 9 on the end of them.

Similarly, this is why line 4 adds 1 to the first value of both and .

Wait, are you saying .sort() converts all the values to strings? Shouldn’t the first value be “11”, not 2?

No. Remember that after sorting and pushing, the values of were . We then called — add the number 1 to the first value of the array.

In JavaScript, adding a string and a number will convert both sides to strings before adding. As such:

However, note though that only converts to strings for the purpose of sorting — the original value is then placed back in. As such, numbers remain numbers, functions remain functions and so on. This means it really is just .

We used const to declare A B and C, and then mutated these values. What’s happening there?

in JavaScript (specifically ES6 / ES2015) refers to the declaration of a variable, and does not act the same way as it would in C++.

What is happening here?

As shown in the above StackOverflow answer, you may mutate or reassign a property on a const variable, but you may not reassign the variable itself.

For something more similar to C++’s understanding of const, please see Object.freeze(myObject);.

Image for post
Image for post
https://unsplash.com/photos/qDY9ahp0Mto — Astonishing code can make us all feel a bit less confident in our abilities.

The Principle Of Least Astonishment

This post is not designed to point at JavaScript and laugh, or say it’s a bad language. It’s old and has evolved over time, while keeping backwards compatibility along the way — sadly this means that newer developers may run into problems there were created years ago.

Instead, I want to encourage you to consider how others will read and use your own code. This post highlights how a few decisions that are confusing can combine very rapidly to make almost impenetrable code, despite it appearing so simple.

Be sure to involve many other engineers, of various seniority levels, in the design of systems that you expect others to use. (Hint: no matter how obscure or small your piece of code, someone else will find it and attempt to use it.)

Please let me know in the comments if you’ve come across any other astonishing code, and be sure to 👏 if you learned something from or enjoyed this article!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store