The symmetric difference of several arrays is the set of values that belong to exactly one of them — everything except the values they share. It is the array-level version of XOR, the "one or the other, but not both" operation, and lodash exposes it as _.xor. You keep the loners and drop anything that shows up in more than one list.
Implement arrayXor(...arrays) that takes any number of arrays and returns a new array of the values appearing in exactly one input array. Duplicates are removed, and the surviving values stay in the order they first appear across the inputs.
function arrayXor(...arrays) {
// returns the values that appear in exactly one of the given arrays
}
arrayXor([2, 1], [2, 3]);
// → [1, 3] // 2 is in both arrays, so it drops out
arrayXor([1, 2], [4, 2], [2, 3]);
// → [1, 4, 3] // 2 appears in all three, so it is excluded
arrayXor([1, 1, 2], [2]);
// → [1] // duplicates inside one array collapse; 2 is shared
[1, 1] contributes the value 1 a single time.SameValueZero — the rule Set and Array.includes use. NaN equals NaN, and 0 equals -0. The number 1 and the string '1' are different values.arrayXor() returns []; a single array returns its unique values.You'll count how many separate arrays each value shows up in, then keep only the values that landed in exactly one of them.
You have a handful of lists and you want the values that are unique to a single list — the ones no other list also contains. Think of three friends' music libraries: the songs only one person owns are the "symmetric difference." Anything two or more people share is out. A value repeated inside one library still counts as just that one person owning it, and the answer keeps each surviving value once, in the order you first meet it.
Picture each array as a circle. Where circles overlap sit the values two arrays share; the parts that don't overlap hold the values unique to a single array. XOR — short for "exclusive or", meaning one or the other but not both — keeps everything outside every overlap and throws away whatever the circles have in common.
The natural first move is to pour every array into one big list and keep the values that show up only once:
function arrayXorNaive(...arrays) {
const all = arrays.flat(); // one flat list of every value
const total = new Map(); // value -> how many times it occurs in total
for (const value of all) {
total.set(value, (total.get(value) || 0) + 1);
}
return all.filter((value) => total.get(value) === 1);
}
This passes the headline cases — arrayXorNaive([2, 1], [2, 3]) gives [1, 3] — but it counts occurrences, not arrays. The moment a value repeats inside a single array, the count is wrong. arrayXorNaive([1, 1, 2], [2]) flattens to [1, 1, 2, 2], sees 1 twice, and drops it — but 1 lives in only one array, so the correct answer is [1]. The fix is to dedupe each array before counting, and to count how many distinct arrays contain a value rather than how many total copies exist.
Two passes: one to count distinct arrays per value, one to collect the values with a count of exactly one.
function arrayXor(...arrays) {
// First pass: count how many DISTINCT arrays each value appears in.
// A fresh `seenHere` Set per array collapses repeats inside that array,
// so [1, 1] bumps the count for 1 only once.
const arrayCount = new Map(); // value -> number of arrays containing it
for (const array of arrays) {
const seenHere = new Set();
for (const value of array) {
if (seenHere.has(value)) continue; // already counted for this array
seenHere.add(value);
// Map keys use SameValueZero, so NaN matches NaN and -0 matches 0.
arrayCount.set(value, (arrayCount.get(value) || 0) + 1);
}
}
// Second pass: keep values that landed in exactly one array, in the order
// they first appear. `emitted` guards against pushing a value twice.
const result = [];
const emitted = new Set();
for (const array of arrays) {
for (const value of array) {
if (emitted.has(value)) continue;
if (arrayCount.get(value) === 1) {
emitted.add(value);
result.push(value);
}
}
}
return result;
}
module.exports = { arrayXor };
Two shifts from the naive version. First, the per-array seenHere set makes duplicates inside one array count once — the question is "how many arrays?", not "how many copies?". Second, both the count map and the emitted set key values with SameValueZero — the same equality Set and Array.prototype.includes use — so NaN, -0, and 0 behave the way callers expect without any special-case code.
Trace arrayXor([1, 2], [4, 2], [2, 3]). Call the arrays A, B, and C.
Pass 1 — count distinct arrays:
[1, 2]: 1 is new → count {1: 1}. 2 is new → {1: 1, 2: 1}.[4, 2]: 4 is new → {…, 4: 1}. 2 is new to B → its count rises to 2.[2, 3]: 2 is new to C → its count rises to 3. 3 is new → {…, 3: 1}.1 → 1, 2 → 3, 4 → 1, 3 → 1.Pass 2 — keep count === 1, in first-appearance order:
1 has count 1 → push. 2 has count 3 → skip.4 has count 1 → push. 2 → skip.2 → skip. 3 has count 1 → push.[1, 4, 3]. The value 2, shared by all three arrays, never makes it in.[1, 1, 2] has two 1s, but 1 belongs to a single array — dedupe each array with its own seen set before counting.arrayXor([1, 2], [4, 2], [2, 3]) excludes 2 even though it appears in three arrays (odd) — because three is not exactly one. Test count === 1, never count % 2 === 1.indexOf or === for membership — strict equality treats NaN as not equal to itself, so a NaN would never match across arrays and would slip through as unique. Set and Map keys use SameValueZero, which treats NaN as equal to NaN — key your lookups by value in a Set/Map instead.arrayXor([3, 1, 2], [2]) is [3, 1], not [1, 3]. Emit values as you scan and guard re-emits with an emitted set rather than sorting at the end.xorBy with an iteratee — lodash's _.xorBy runs each value through a function first, so you can XOR objects by a field: _.xorBy([{ x: 1 }], [{ x: 2 }, { x: 1 }], 'x') compares by x instead of by reference. Swap the raw value keys for iteratee(value) keys in both passes to build it.union keeps values in any array, intersection keeps values in all arrays, and difference keeps values in the first array but none of the rest. XOR is the "exactly one" member of that family. Contrasting the four is a common interview follow-up.^) sets a bit when its two inputs differ, which for two values means "one or the other, but not both". Lifted to two arrays with no internal repeats, that is exactly "value in one array but not the other". The analogy is clean for two inputs; with three or more, lodash sticks with "in exactly one array" rather than the bit-level "odd number of arrays".Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
The symmetric difference of several arrays is the set of values that belong to exactly one of them — everything except the values they share. It is the array-level version of XOR, the "one or the other, but not both" operation, and lodash exposes it as _.xor. You keep the loners and drop anything that shows up in more than one list.
Implement arrayXor(...arrays) that takes any number of arrays and returns a new array of the values appearing in exactly one input array. Duplicates are removed, and the surviving values stay in the order they first appear across the inputs.
function arrayXor(...arrays) {
// returns the values that appear in exactly one of the given arrays
}
arrayXor([2, 1], [2, 3]);
// → [1, 3] // 2 is in both arrays, so it drops out
arrayXor([1, 2], [4, 2], [2, 3]);
// → [1, 4, 3] // 2 appears in all three, so it is excluded
arrayXor([1, 1, 2], [2]);
// → [1] // duplicates inside one array collapse; 2 is shared
[1, 1] contributes the value 1 a single time.SameValueZero — the rule Set and Array.includes use. NaN equals NaN, and 0 equals -0. The number 1 and the string '1' are different values.arrayXor() returns []; a single array returns its unique values.You'll count how many separate arrays each value shows up in, then keep only the values that landed in exactly one of them.
You have a handful of lists and you want the values that are unique to a single list — the ones no other list also contains. Think of three friends' music libraries: the songs only one person owns are the "symmetric difference." Anything two or more people share is out. A value repeated inside one library still counts as just that one person owning it, and the answer keeps each surviving value once, in the order you first meet it.
Picture each array as a circle. Where circles overlap sit the values two arrays share; the parts that don't overlap hold the values unique to a single array. XOR — short for "exclusive or", meaning one or the other but not both — keeps everything outside every overlap and throws away whatever the circles have in common.
The natural first move is to pour every array into one big list and keep the values that show up only once:
function arrayXorNaive(...arrays) {
const all = arrays.flat(); // one flat list of every value
const total = new Map(); // value -> how many times it occurs in total
for (const value of all) {
total.set(value, (total.get(value) || 0) + 1);
}
return all.filter((value) => total.get(value) === 1);
}
This passes the headline cases — arrayXorNaive([2, 1], [2, 3]) gives [1, 3] — but it counts occurrences, not arrays. The moment a value repeats inside a single array, the count is wrong. arrayXorNaive([1, 1, 2], [2]) flattens to [1, 1, 2, 2], sees 1 twice, and drops it — but 1 lives in only one array, so the correct answer is [1]. The fix is to dedupe each array before counting, and to count how many distinct arrays contain a value rather than how many total copies exist.
Two passes: one to count distinct arrays per value, one to collect the values with a count of exactly one.
function arrayXor(...arrays) {
// First pass: count how many DISTINCT arrays each value appears in.
// A fresh `seenHere` Set per array collapses repeats inside that array,
// so [1, 1] bumps the count for 1 only once.
const arrayCount = new Map(); // value -> number of arrays containing it
for (const array of arrays) {
const seenHere = new Set();
for (const value of array) {
if (seenHere.has(value)) continue; // already counted for this array
seenHere.add(value);
// Map keys use SameValueZero, so NaN matches NaN and -0 matches 0.
arrayCount.set(value, (arrayCount.get(value) || 0) + 1);
}
}
// Second pass: keep values that landed in exactly one array, in the order
// they first appear. `emitted` guards against pushing a value twice.
const result = [];
const emitted = new Set();
for (const array of arrays) {
for (const value of array) {
if (emitted.has(value)) continue;
if (arrayCount.get(value) === 1) {
emitted.add(value);
result.push(value);
}
}
}
return result;
}
module.exports = { arrayXor };
Two shifts from the naive version. First, the per-array seenHere set makes duplicates inside one array count once — the question is "how many arrays?", not "how many copies?". Second, both the count map and the emitted set key values with SameValueZero — the same equality Set and Array.prototype.includes use — so NaN, -0, and 0 behave the way callers expect without any special-case code.
Trace arrayXor([1, 2], [4, 2], [2, 3]). Call the arrays A, B, and C.
Pass 1 — count distinct arrays:
[1, 2]: 1 is new → count {1: 1}. 2 is new → {1: 1, 2: 1}.[4, 2]: 4 is new → {…, 4: 1}. 2 is new to B → its count rises to 2.[2, 3]: 2 is new to C → its count rises to 3. 3 is new → {…, 3: 1}.1 → 1, 2 → 3, 4 → 1, 3 → 1.Pass 2 — keep count === 1, in first-appearance order:
1 has count 1 → push. 2 has count 3 → skip.4 has count 1 → push. 2 → skip.2 → skip. 3 has count 1 → push.[1, 4, 3]. The value 2, shared by all three arrays, never makes it in.[1, 1, 2] has two 1s, but 1 belongs to a single array — dedupe each array with its own seen set before counting.arrayXor([1, 2], [4, 2], [2, 3]) excludes 2 even though it appears in three arrays (odd) — because three is not exactly one. Test count === 1, never count % 2 === 1.indexOf or === for membership — strict equality treats NaN as not equal to itself, so a NaN would never match across arrays and would slip through as unique. Set and Map keys use SameValueZero, which treats NaN as equal to NaN — key your lookups by value in a Set/Map instead.arrayXor([3, 1, 2], [2]) is [3, 1], not [1, 3]. Emit values as you scan and guard re-emits with an emitted set rather than sorting at the end.xorBy with an iteratee — lodash's _.xorBy runs each value through a function first, so you can XOR objects by a field: _.xorBy([{ x: 1 }], [{ x: 2 }, { x: 1 }], 'x') compares by x instead of by reference. Swap the raw value keys for iteratee(value) keys in both passes to build it.union keeps values in any array, intersection keeps values in all arrays, and difference keeps values in the first array but none of the rest. XOR is the "exactly one" member of that family. Contrasting the four is a common interview follow-up.^) sets a bit when its two inputs differ, which for two values means "one or the other, but not both". Lifted to two arrays with no internal repeats, that is exactly "value in one array but not the other". The analogy is clean for two inputs; with three or more, lodash sticks with "in exactly one array" rather than the bit-level "odd number of arrays".Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.