How to Compare Two Arrays in Google Apps Script

Apps Script runs on the same V8 JavaScript engine as Chrome, so the array comparison logic is plain JavaScript - the part that actually trips people up is getting data in and out of a Google Sheet in the right shape before and after comparing it.

Quick answer

const setB = new Set(b); const aOnly = a.filter(x => !setB.has(x)); is the fast approach, same as any JavaScript array comparison. The Apps Script-specific gotcha is that Range.getValues() always returns a 2D array - flatten it or map to a single column first, e.g. range.getValues().map(row => row[0]), before running the comparison.

The comparison itself: plain JavaScript

const a = ['apple', 'banana', 'cherry'];
const b = ['banana', 'date'];

const setB = new Set(b);
const aOnly = a.filter(x => !setB.has(x));

Logger.log(aOnly); // [apple, cherry]

This is identical to the standard JavaScript approach - build a Set from one array, filter the other against it. See the JavaScript array difference guide for symmetric difference and other variations, all of which work the same way inside Apps Script.

The gotcha: getValues() returns a 2D array

const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
const range = sheet.getRange('A2:A100');
const raw = range.getValues();
// raw looks like [['apple'], ['banana'], ['cherry'], ...] - NOT ['apple', 'banana', 'cherry']

const colA = raw.map(row => row[0]).filter(v => v !== '');
// now colA is a flat array, with blank trailing rows removed

Even a single-column range comes back as an array of rows, each row an array of that row's column values. Comparing two raw getValues() results directly against each other with a Set produces nothing useful, since each "item" is itself a one-element array. Map to row[0] (or use .flat() for a single column range) before doing anything else with the data.

A complete function: compare two sheet columns

function compareColumns() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('Sheet1');

  const lastRowA = sheet.getRange('A:A').getValues().filter(String).length;
  const lastRowB = sheet.getRange('B:B').getValues().filter(String).length;

  const colA = sheet.getRange(1, 1, lastRowA, 1).getValues().flat();
  const colB = sheet.getRange(1, 2, lastRowB, 1).getValues().flat();

  const setA = new Set(colA);
  const setB = new Set(colB);

  const aOnly = colA.filter(x => !setB.has(x));
  const bOnly = colB.filter(x => !setA.has(x));

  const resultsSheet = ss.getSheetByName('Results') || ss.insertSheet('Results');
  resultsSheet.clear();
  resultsSheet.getRange(1, 1, 1, 2).setValues([['A Only', 'B Only']]);
  if (aOnly.length) {
    resultsSheet.getRange(2, 1, aOnly.length, 1).setValues(aOnly.map(v => [v]));
  }
  if (bOnly.length) {
    resultsSheet.getRange(2, 2, bOnly.length, 1).setValues(bOnly.map(v => [v]));
  }
}

This reads column A and column B from Sheet1, computes both A-only and B-only, and writes the results to a new tab called Results. Both reads and both writes are batched into single calls rather than looped cell-by-cell, which is the single biggest performance factor in any Apps Script that touches a range of any real size.

Comparing two Google Sheets columns without writing a script?

See the formula-based approach, plus a faster alternative.

Read the guide →

Just need the answer, not the script? Paste both lists.

Open Compare 2 Lists →

Frequently Asked Questions

What is the fastest way to compare two arrays in Google Apps Script?
The same approach as plain JavaScript, since Apps Script runs on a JavaScript engine: build a Set from one array and filter the other against it. const setB = new Set(b); const aOnly = a.filter(x => !setB.has(x));. This is O(n + m) instead of the O(n * m) you get from a.filter(x => !b.includes(x)) on large arrays.
Why does comparing two Google Sheets columns in Apps Script give me weird nested array errors?
Range.getValues() always returns a 2D array - an array of rows, each row itself an array of column values - even for a single column. Comparing that directly against another 2D array does nothing useful. Flatten it first with .flat() or map to the single column you need: const colA = range.getValues().map(row => row[0]);.
Is indexOf or includes() ever fine to use in Apps Script instead of a Set?
For small ranges - a few dozen to low hundreds of rows - the performance difference between .includes() and a Set is not noticeable in practice, and script execution time is dominated by getValues()/setValues() calls anyway. Switch to a Set once either list has more than a few hundred rows, or if the script runs on a trigger and needs to stay fast.
How do I write the results back to a Google Sheet from Apps Script?
Build a 2D array (array of single-element arrays for one column) and call setValues() on a range sized to match: sheet.getRange(1, 1, results.length, 1).setValues(results.map(v => [v]));. setValues() requires the array dimensions to exactly match the target range's row and column count.
Should I read and write cells one at a time in a loop instead?
No - calling getRange().getValue() or setValue() inside a loop is dramatically slower than one getValues() call and one setValues() call outside the loop, because each individual call is a round trip to the Sheets service. Always batch reads and writes with getValues()/setValues() and do the comparison logic on the in-memory arrays.
How do I compare two columns case-insensitively in Apps Script?
Normalize before building the Set: const setB = new Set(colB.map(x => String(x).toLowerCase())); then filter colA the same way. Wrapping in String() first avoids an error if a cell contains a number rather than text.
Can I trigger this comparison automatically when the sheet is edited?
Yes - use an installable onEdit(e) trigger (Apps Script menu > Triggers > Add Trigger, event type On edit) that calls your comparison function whenever the relevant range changes, rather than requiring someone to manually run the script.

Share this tool

Featured on ToolDirs
© 2026 ListDiff

Free online list and text processing tools.