Comparing Arrays in JavaScript

Abstract image used to represent Comparing Arrays in JavaScript
Image by Scott Webb.

Much like manipulating array data by appending and prepending elements, comparing two arrays in JavaScript is a very common task in frontend development but as is always the case there are a number of different options and methods to achieve this comparison, all with different benefits and drawbacks. Before choosing a method, decide what equal means. Are we checking whether both variables refer to the same array, or whether two separate arrays contain matching values in the same order? Here, we are interested in the second question.


Using the toString() method

Converting both arrays to strings looks like a quick comparison, and it gives the expected result for this simple example. It loses information, though: [1, 2] and ["1,2"] both become "1,2". Matching strings do not prove that the original arrays match:

const array1 = [1, 2, 3, 4];
const array2 = [1, 2, 3, 4];
const areEqual = array1.toString() === array2.toString();

console.log(areEqual ? 'The arrays are equal' : 'The arrays are not equal');
//=> 'The arrays are equal'

Using the JSON.stringify() method

We can also serialise both arrays with JSON.stringify() and compare the results. JSON preserves more structure than toString(), but the comparison is only useful when its conversions match the values we intend to distinguish:

const array1 = [1, 2, 3, 4];
const array2 = [1, 2, 3, 4];
const areEqual = JSON.stringify(array1) === JSON.stringify(array2);

console.log(areEqual ? 'The arrays are equal' : 'The arrays are not equal');
//=> 'The arrays are equal'

JSON.stringify() converts both [undefined] and [null] to "[null]", so those different arrays compare equal after serialisation. Functions in arrays and nonfinite numbers also become null. Circular references are different again: serialising them throws a TypeError:

TypeError: Converting circular structure to JSON

Use a Comparative, Recursive Function

The final and potentially most robust option is to write a recursive, looping function to do a deep comparison of each array by looping over each individual element. This is best achieved in a utility function, with some simple gatekeeping. It could look something like this:

const arraysAreEqual = (a, b) => {
  const isArrayA = Array.isArray(a);
  const isArrayB = Array.isArray(b);

  // type check: if one is an array and the other isn't
  if (isArrayA !== isArrayB) {
    return false;
  }

  // strict equality check (if neither are arrays)
  if (!(isArrayA && isArrayB)) {
    return a === b;
  }

  // at this point we know they are both arrays, so use a
  // simple length check to see if they are equal.
  if (a.length !== b.length) {
    return false;
  }

  // Compare ownership and values at every numeric index.
  for (let index = 0; index < a.length; index += 1) {
    const aHasIndex = Object.prototype.hasOwnProperty.call(a, index);
    const bHasIndex = Object.prototype.hasOwnProperty.call(b, index);

    if (aHasIndex !== bHasIndex) return false;
    if (aHasIndex && !arraysAreEqual(a[index], b[index])) return false;
  }

  return true;
};

console.log(arraysAreEqual([1, 2, 3, 4], [1, 2, 3, 4]));
//=> true

console.log(arraysAreEqual([1, [2, 3]], [1, [2, 4]]));
//=> false

I've added comments to the snippet above to offer some guidance, but to describe what's going on in more detail: this function accepts two values and returns true if they are equal, and false otherwise.

In order to keep the function as performant as possible, we want to return false as early as we can, so we work our way through progressively more complex comparisons:

  1. First, we check if both parameters passed are arrays.
  2. We can immediately return false if we know that the parameter types don't match (for example if one is an array and the other is a string).
  3. If neither are arrays, we return a strict equality check between the two parameters (this is useful when it comes to calling the function recursively later on).
  4. We now know that both parameters are arrays, so we compare their lengths; naturally, if they are of different lengths then they cannot be equal, so we return false.
  5. At each index, we check whether both arrays own an element there. A missing slot is different from an element containing undefined. If ownership differs, or the two present values fail the recursive comparison, we return false.
  6. Finally, if all of the above has passed, then we can be sure that the two arrays match and we can return true.

It is important to note that this function assumes that the arrays do not contain circular references and that their elements are either primitives or arrays themselves. If you need to handle more complex cases, you would need to modify the function accordingly.

A try/catch can catch a failure, but it cannot tell us whether two circular structures are equal. Keep this helper for the stated primitiveandarray inputs without cycles. If your data can contain cycles or other object types, choose an implementation that explicitly supports them.


Choosing the Right Solution

As I mentioned, each method of array comparison in JavaScript has its own benefits and drawbacks, and only you will know which is most suitable for your specific use case. Here are a few factors worth considering:

Speed

If comparison speed matters, measure it with representative inputs. A loop can stop at the first difference, whereas serialisation has to produce strings first. The input shape and where differences occur matter more than a blanket claim that one method is always faster.

Complexity

Nested arrays, sparse slots, and special values need deliberate rules. JSON.stringify() is convenient for data with a tightly controlled JSON shape, but it is not a general equality function. The recursive helper makes the index and value checks explicit.

Flexibility

Some methods may be more flexible than others, allowing you to customise the comparison logic to your specific needs. Using (and modifying) the recursive, looping example I've given above may give you the most control over how the arrays are compared, but it will require more code to handle complex cases and may be less performant.

Readability

Some methods may be more readable than others, making your code easier to understand and maintain. Using JSON.stringify() or toString() may be more concise and easier to read than a much larger utility function, especially if you don't need to handle complex cases.


The Wrap‑up

Ultimately, the best method for comparing arrays depends on your specific use case and requirements. You should consider factors such as speed, complexity, flexibility, and readability when choosing which method to use, but hopefully, the three examples I've shared today will help you along the way.


Have a complex web platform issue?

Tell me what is blocked, what has changed, and what needs to be true after the fix. I'll come back with a practical next step.