Skip to content

/commands/javascript/remove-duplicates-from-array

javascript remove duplicates from array

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · v24.20.0updated
tool
JavaScript
versions
ES2015+ (Set), Node.js 0.12+
os
any

The shortest way to remove duplicates from an array is [...new Set(arr)]: a Set holds each value once, and spreading it back gives an array in first-seen order. It works for strings, numbers, booleans, null, undefined and NaN. For arrays of objects, deduplicate by a key (for example id) with a Map.

Primitive values

bash
cat > dedupe.mjs <<'EOF'
const values = [3, "3", 1, 3, NaN, NaN, null, null, "a", "A", "a"];
console.log([...new Set(values)]);
console.log(Array.from(new Set(values)).length, "unique values");
console.log(values.filter((v, i) => values.indexOf(v) === i));
EOF
node dedupe.mjs

Output

[
  3,   '3',  1,
  NaN, null, 'a',
  'A'
]
7 unique values
[ 3, '3', 1, null, 'a', 'A' ]

Set uses SameValueZero equality: 3 and "3" are different values, "a" and "A" are different, and NaN counts as equal to itself. The filter + indexOf version drops every NaN, because indexOf uses === and NaN === NaN is false.

Objects by key

bash
cat > objects.mjs <<'EOF'
const rows = [
  { id: 1, name: "first" },
  { id: 2, name: "second" },
  { id: 1, name: "first again" },
];
console.log("Set on objects:", new Set(rows).size);
const keepLast = [...new Map(rows.map((r) => [r.id, r])).values()];
console.log("keep last:", keepLast);
const seen = new Set();
const keepFirst = rows.filter((r) => !seen.has(r.id) && seen.add(r.id));
console.log("keep first:", keepFirst);
EOF
node objects.mjs

Output

Set on objects: 3
keep last: [ { id: 1, name: 'first again' }, { id: 2, name: 'second' } ]
keep first: [ { id: 1, name: 'first' }, { id: 2, name: 'second' } ]

A Set of objects removes nothing, because every object literal is a distinct reference. Building a Map keyed by id keeps the last object for each key; the filter with a seen set keeps the first.

Methods compared

MethodWorks onNotes
[...new Set(arr)]PrimitivesFast, keeps first-seen order, treats NaN as one value
Array.from(new Set(arr))PrimitivesSame result; useful where spread syntax is not available
arr.filter((v, i) => arr.indexOf(v) === i)PrimitivesQuadratic time; drops all NaN values
[...new Map(arr.map(o => [o.id, o])).values()]ObjectsOne object per key, the last one wins
filter with a seen SetObjectsOne object per key, the first one wins
Lodash _.uniqBy(arr, "id")ObjectsLibrary helper, keeps the first

Pitfalls

  • Case and type differences are real differences for Set. Normalise first: [...new Set(arr.map(s => s.trim().toLowerCase()))].
  • Spreading a Set needs ES2015 iteration; when TypeScript targets ES5 without downlevelIteration, use Array.from(new Set(arr)) instead.
  • Calling array methods directly on a Set (new Set(arr).map(...)) fails with x is not a function: convert it back to an array first.

Confirm it worked

Compare lengths: arr.length against new Set(arr).size tells you how many duplicates there were before you remove them, and the two are equal afterwards.

FAQ

Does new Set() keep the original order?

Yes. A Set iterates its values in insertion order, so [...new Set(arr)] keeps the first occurrence of each value in the position where it first appeared.

Why are two identical objects not removed by Set?

A Set compares objects by reference, not by content. Two separate objects with the same fields are different values. Deduplicate by a key such as id with a Map, as shown above.

Is filter with indexOf slower than Set?

Yes for large arrays. indexOf scans the array for every element, which is quadratic. A Set lookup is close to constant time, so [...new Set(arr)] scales to large arrays.

Sources