/commands/javascript/remove-duplicates-from-array
javascript remove duplicates from array
- 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
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.mjsOutput
[ 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
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.mjsOutput
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
| Method | Works on | Notes |
|---|---|---|
| [...new Set(arr)] | Primitives | Fast, keeps first-seen order, treats NaN as one value |
| Array.from(new Set(arr)) | Primitives | Same result; useful where spread syntax is not available |
| arr.filter((v, i) => arr.indexOf(v) === i) | Primitives | Quadratic time; drops all NaN values |
| [...new Map(arr.map(o => [o.id, o])).values()] | Objects | One object per key, the last one wins |
| filter with a seen Set | Objects | One object per key, the first one wins |
| Lodash _.uniqBy(arr, "id") | Objects | Library 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, useArray.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
- developer.mozilla.orgdeveloper.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
- developer.mozilla.orgdeveloper.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
- developer.mozilla.orgdeveloper.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness