All Mixed Up: Isomorphic Sorting Gone Wrong

Written by Paul Hebert on

Illustration of interface cards being sorted by icon

As a JavaScript developer, I’m used to my code running differently in different environments. But earlier this week, I ran into a problem that left me scratching my head for most of the morning.

I was working on an isomorphic JavaScript application and noticed that the JavaScript sort() function was returning different results on a Node server compared to in the browser.

An Example

Here’s a test case illustrating the problem. We’re fetching 30 of Cloud Four’s GitHub repos, sorting them by programming language, and logging them to the console:

// Fetch our data from the Github API
fetch("https://api.github.com/users/cloudfour/repos")
  .then(resp => resp.json())
  .then(repos => {
    // Remove all the extra fields besides name and language
    const cloudFourRepos = repos.map(({ name, language }) => ({
      name, 
      language: String(language),
    }))
    // Sort by programming language
    .sort((a, b) => {
      if (a.language === b.language) {
        return 0;
      }
      return a.language < b.language ? -1 : 1;
    });

    console.table(cloudFourRepos);
  });

Whether I run this in Node 1 or the browser, the sort() function correctly groups the repos by language. But inside of those groups, things start to get a little weird.

To figure out what’s going on, let’s drill down and only look at the HTML repos. Here they are unsorted, as well as sorted by Node and the browser. 2 The browser has left them in the same relative sort order, while Node has scrambled them all up!

Unsorted Sorted in Browser Sorted in Node
cloudfour.com-patterns cloudfour.com-patterns pwa.rocks
drizzle drizzle cloudfour.com-patterns
leveller leveller drizzle
pwa-workshop pwa-workshop pwa-workshop
pwa.rocks pwa.rocks leveller

Wait, What?

What’s going on here? Why is the same sort() function behaving so differently in different environments?

It turns out that when the compare function returns 0 (two elements match), it’s up to the JavaScript engine to choose how to handle it!

If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different elements. Note: the ECMAscript standard does not guarantee this behavior, thus, not all browsers (e.g. Mozilla versions dating back to at least 2003) respect this.

Stability in Sorting

The difference we’re seeing here is called “sort stability.” A stable sort function will leave the order of the elements the same if they match under the sorting criteria. An unstable sorting criteria on the other hand, is likely to rearrange those matching elements.

Older versions of Node (everything before Node 11) and older browsers used unstable sorting algorithms while most modern browsers use stable sorts.

Who Cares?

Sometimes stable sorting doesn’t matter, but it’s important to be aware of the difference.

Stable sorting can allow you to chain sorting functions, sorting first by one characteristic and then by another.

Also, it can be confusing for users if a page renders differently depending on whether it’s rendered on the server or in the browser. With some isomorphic application frameworks this can lead to hydration errors and — in one particularly odd bug I ran into recently — switch the photos on user profiles.

Regaining Some Stability

We have a few options to ensure our sort option returns the same array regardless of the JavaScript engine we’re using and whether it uses a stable sort.

Manually Index

One way to make our sort stable is to loop over the array and add keys to each item based on their index. This allows us to sort by their index after sorting by our primary criteria. Here it is in Node and the browser:

const cloudFourRepos = repos.map(({ name, language }, index) => ({
  name,
  language: String(language),
  index,
}))
// Sort by programming language
.sort((a, b) => {
  if (a.language === b.language) {
    return a.index < b.index ? -1 : 1;
  }
  return a.language < b.language ? -1 : 1;
});

Sort by a Secondary Characteristic

If we’re not worried about stability, but just want our sort to match between Node and the browser, we have another option. If our array items already have a unique key like an ID, we can sort by that property after sorting by our primary sort criteria. Our Github repos have IDs so we can do this in Node and in the browser:

const cloudFourRepos = repos.map(({ name, language, id }) => ({
  name, 
  language: String(language),
  id,
}))
// Sort by programming language
.sort((a, b) => {
  if (a.language === b.language) {
    return a.id < b.id ? -1 : 1;
  }
  return a.language < b.language ? -1 : 1;
});

What We’ve Learned

Debugging this issue was a good reminder to always consider the environment your code is running in and to question your assumptions about how things work.

From now on whenever I sort objects in JavaScript I’ll consider whether I need stable sorting and whether my sort function will work the same in every environment.


  1. To use fetch in Node you’ll need to install and require node-fetch
  2. The list of repos may have changed since this article was written. 
Paul Hebert

Paul Hebert is a hybrid designer and developer at Cloud Four. When he's not designing and developing websites he enjoys bouldering, drawing, cooking, gardening, and eating too much cheese.

Never miss an article!

Get Weekly Digests


Leave a Comment

Please be kind, courteous and constructive. You may use simple HTML or Markdown in your comments. All fields are required.


Let’s discuss your project! Email Us