Skip to content
JS
Recipe · Arrays

Remove duplicates from an array in JavaScript

Updated 25 Sept 20266 methodsRuns in browsers and Node.js
JavaScript
const unique = [...new Set(array)];

Keeps the first occurrence of each value in its original order, returns a new array without changing the original, and compares values with SameValueZero.

Try it

Edit the input or pick a method
Method
Output
[3, "a", 1, "b", 2, "1"]

9 items in, 6 out (3 removed). Runs in your browser.

MethodMutates inputTimeSupportUse when
Set with spreadNoO(n)ES2015. All current browsers and Node.js. Not Internet Explorer.Primitive values (numbers, strings, booleans)
Array.from with SetNoO(n)ES2015. All current browsers and Node.js. Not Internet Explorer.TypeScript projects that compile to ES5 without downlevelIteration
filter with indexOfNoO(n²)ES5. All browsers including Internet Explorer 9+.Short arrays in code that must run without ES2015
reduce with includesNoO(n²)ES2016 (Array.prototype.includes). All current browsers and Node.js. Not Internet Explorer.Building the result alongside other work in a single reduce
Objects by propertyNoO(n)ES2015. All current browsers and Node.js. Not Internet Explorer.Arrays of objects that share an identifying key
lodash uniq / uniqByNoO(n) for arrays of 200+ items, O(n²) below thatlodash 4Codebases that already depend on lodash

1.Set with spread

A Set stores each value once, so passing the array to new Set() drops duplicates. Spreading the Set into an array literal returns the values in insertion order, which is the order of first occurrence.

[...new Set(arr)]
const numbers = [3, 1, 3, 2, 1];
const unique = [...new Set(numbers)];
// [3, 1, 2]

2.Array.from with Set

Gives the same result as the spread version. Array.from() takes an optional mapping function as its second argument, so you can transform values in the same step.

Array.from(new Set(arr))
const numbers = [3, 1, 3, 2, 1];
const unique = Array.from(new Set(numbers));
// [3, 1, 2]

3.filter with indexOf

Keeps an element only when indexOf() finds its first occurrence at the current index. indexOf() compares with strict equality, so NaN is never found and every NaN is removed from the result.

arr.filter((v, i, a) => a.indexOf(v) === i)
const numbers = [3, 1, 3, 2, 1];
const unique = numbers.filter((value, index, array) => array.indexOf(value) === index);
// [3, 1, 2]

4.reduce with includes

Adds each value to the accumulator only if the accumulator does not already include it. includes() uses SameValueZero, so NaN is kept once.

arr.reduce((acc, v) => acc.includes(v) ? acc : [...acc, v], [])
const numbers = [3, 1, 3, 2, 1];
const unique = numbers.reduce((acc, value) => {
  if (!acc.includes(value)) acc.push(value);
  return acc;
}, []);
// [3, 1, 2]

5.Objects by property

Tracks the keys already seen in a Set and keeps the first object for each key. To keep the last object for each key instead, use [...new Map(arr.map((o) => [o.id, o])).values()].

arr.filter(o => !seen.has(o.id) && seen.add(o.id))
const users = [
  { id: 1, name: "Ada" },
  { id: 2, name: "Grace" },
  { id: 1, name: "Ada L." },
];

const seen = new Set();
const unique = users.filter((user) => {
  if (seen.has(user.id)) return false;
  seen.add(user.id);
  return true;
});
// [{ id: 1, name: "Ada" }, { id: 2, name: "Grace" }]

6.lodash uniq / uniqBy

_.uniq() keeps the first occurrence of each value using SameValueZero. _.uniqBy() takes a property name or a function and compares the values it returns.

_.uniq(arr) / _.uniqBy(arr, 'id')
import uniq from "lodash/uniq";
import uniqBy from "lodash/uniqBy";

uniq([3, 1, 3, 2, 1]);
// [3, 1, 2]

uniqBy([{ id: 1 }, { id: 2 }, { id: 1 }], "id");
// [{ id: 1 }, { id: 2 }]
function unique(items) {
  return [...new Set(items)];
}

function uniqueBy(items, key) {
  const seen = new Set();
  return items.filter((item) => {
    const k = key(item);
    if (seen.has(k)) return false;
    seen.add(k);
    return true;
  });
}

unique([3, 1, 3, 2]); // [3, 1, 2]
uniqueBy([{ id: 1 }, { id: 1 }], (u) => u.id); // [{ id: 1 }]

Objects are compared by reference

Set, indexOf() and includes() treat two objects as equal only if they are the same object. Objects with identical contents are all kept. Deduplicate by a key instead.

Example
[...new Set([{ a: 1 }, { a: 1 }])].length; // 2

const obj = { a: 1 };
[...new Set([obj, obj])].length; // 1

NaN depends on the method

Set, includes() and lodash use SameValueZero, which treats NaN as equal to NaN, so one NaN is kept. indexOf() uses strict equality and returns -1 for NaN, so the filter + indexOf method removes every NaN.

Example
const values = [1, NaN, 2, NaN];

[...new Set(values)];                              // [1, NaN, 2]
values.filter((v, i, a) => a.indexOf(v) === i);    // [1, 2]
values.reduce((acc, v) => (acc.includes(v) ? acc : [...acc, v]), []); // [1, NaN, 2]

-0 and +0 are the same value

SameValueZero treats -0 and +0 as equal, so only one is kept. A Set also stores -0 as +0, so the result contains 0 even if -0 came first.

Example
[...new Set([-0, 0])];                // [0]
Object.is([...new Set([-0])][0], -0); // false

Different types are kept separate

No type conversion happens, so the number 1 and the string "1" are different values. Convert values first if they should match.

Example
[...new Set([1, "1", 1])];          // [1, "1"]
[...new Set([1, "1", 1].map(String))]; // ["1"]

Keeping the first or last occurrence

Every method on this page keeps the first occurrence. A Map keyed by the property keeps the last value for each key, in the position where that key first appeared.

Example
const rows = [{ id: 1, v: "a" }, { id: 2, v: "b" }, { id: 1, v: "c" }];

[...new Map(rows.map((r) => [r.id, r])).values()];
// [{ id: 1, v: "c" }, { id: 2, v: "b" }]

Sparse arrays

Spreading or iterating an array reads holes as undefined, so the Set methods return undefined in place of a hole. filter() and reduce() skip holes, so they drop them.

Example
const sparse = [1, , 1];

[...new Set(sparse)];                           // [1, undefined]
sparse.filter((v, i, a) => a.indexOf(v) === i); // [1]