TypeOfNaN

Exploring the Symmetric Difference Interview Question in JavaScript

Nick Scialli
January 12, 2020

Introduction

The Symmetric Difference interview question is an interesting one as it can be relatively simple to solve if you can think to use the Set object, or, seemingly very challenging or inefficient otherwise.

The Symmetric Difference Question

First, let’s understand the symmetric difference question. It’s usually posed as some form of the following:

You are asked to create a function that finds the symmetric difference of any number of arrays. The symmetric difference of two arrays is calculated by finding all values that are in one array but not the other array. For example, the symmetric difference of [1, 2, 3] and [2, 3, 4] is [1, 4] since the numbers 1 and 4 are each only in one of the two arrays. Importantly, the output array should only have unique values.

When you have more than two arrays, the symmetric difference is calculate from left to right, comparing the next array with the symmetric difference of the previous two. For example, the symmetric difference of [1, 2, 3], [2, 3, 4], and [3, 4, 5] would be calcuated as follows:

  • The symmetric difference of [1, 2, 3] and [2, 3, 4] is [1, 4]
  • The symmetric difference of [1, 4] and [3, 4, 5] is [1, 3, 5]

Therefore, the answer is [1, 3, 5]

Problem Setup

Based on the problem description, our function description might look something like this:

/**
 * @param {number[][]} arrs
 * @return {number[]}
 */
const symDiff = (arrs) => {
  // Solution here
};

Where arrs is an array of arrays of numbers, and our output is an array of numbers.

A Quick Note on Solving Coding Challenges During an Interview

If you’re solving any coding challenge during an interview, it would be prudent to ask some clarifying questions before you start solving the problem. In the symmetric difference case, you might want to ask the following questions (and probably some others I can’t think of):

  • Can the input ever be zero arrays? If so, what is the symmetric difference in that case?
  • Can the input ever be one array? Again, what would the symmetric difference be in that case?
  • Can the input arrays contain anything other than numbers? If so, clarify the behavior in non-number cases.

For the purpose of this blog post, we will assume the input array will always be two or more arrays of numbers.

An Idiomatic JavaScript Solution

Let’s get right to it: the following snippet shows an idiomatic JavaScript solution that combines concepts like the Set object, the reduce array method, the ternary operator, and the spread operator.

const symDiff = (arrs) => {
  arrs[0] = new Set(arrs[0]);
  const diff = arrs.reduce((acc, cur) => {
    const prevNums = new Set();
    cur.forEach((el) => {
      if (prevNums.has(el)) return;
      acc.has(el) ? acc.delete(el) : acc.add(el);
      prevNums.add(el);
    });
    return acc;
  });
  return [...diff];
};

The real star here is the Set object. Let’s dive into how this works.

How it Works

The best way to see how this works is to step through it line-by-line. I will annotate the previous code with comments, explaining each line.

const symDiff = (arrs) => {
  /*
  Mutates the first element of the input array 
  to make it a `Set` object. (Note: it's not 
  necessarily prudent to mutate your input array, 
  but we could ask the interviewer if that's 
  allowed and pivot if it's not).
  */
  arrs[0] = new Set(arrs[0]);
  /*
  Reduce over our input array. The accumulator 
  (acc) will be start out as our Set above and 
  then, in each subsequent iterator, will be the 
  result of the previous symmetric difference!
  */
  const diff = arrs.reduce((acc, cur) => {
    /* 
    Create a Set to track if what numbers have 
    already appeared in the current (cur) array
    */
    const prevNums = new Set();
    /*
    Iterate through each element in the current 
    array so we can check if it's in the 
    accumulator array.
    */
    cur.forEach((el) => {
      /*
      If this number has already shown up in the 
      current array, skip it
      */
      if (prevNums.has(el)) return;
      /*
      If the accumulator contains the current 
      number, then it is in both arrays and cannot 
      be in the symmetric difference. So, delete it 
      from the accumulator. On the other hand, if 
      the current number isn't in the accumulator, 
      it is in the symmetric difference, so add it.
      */
      acc.has(el) ? acc.delete(el) : acc.add(el);
      /*
      Take note that this number has been processed 
      for the current array to make sure we don't 
      evaluate a duplicate value in the future.
      */
      prevNums.add(el);
    });
    /*
    We now have our symmetric difference of the 
    accumulator and the current array! Return the 
    accumulator for evaluation with the next array 
    in line (or to return it from the reduce method 
    if we're done iterating through the arrays)
    */
    return acc;
  });
  /*
  Our output has to be an array, so spread the `diff` 
  set into a new array and return it. Could have 
  alternatively used `Array.from`.
  */
  return [...diff];
};

Conclusion

I like this solution for a couple reasons. It seems to have pretty good time complexity as it requires iterating through the input array-of-arrays exactly once and iterating through each sub-array exactly once. Additionally, it affords you an opportunity to demonstrate knowledge of the Set object and to discuss why it’s beneficial to use (namely, that it has hash-table efficiency to look up an element).

Nick Scialli

Nick Scialli is a senior UI engineer at Microsoft.

© 2024 Nick Scialli