To remove duplicate values from an array of primitives in JavaScript, wrap it in a Set and spread it back into an array:
const unique = [...new Set([1, 2, 2, 3, 3, 3])]; // [1, 2, 3]That is the whole answer for numbers, strings, booleans, null, undefined, and symbols. A Set stores each value at most once, so building one from the array drops the repeats; the spread (...) turns it back into a plain array. It preserves first-seen order, which is the behavior you almost always want.
The rest of this page is the part the one-liner does not tell you: it silently fails on arrays of objects, because Set compares objects by reference, not by contents. If you have [{id: 1}, {id: 1}], Set keeps both. I have watched that "fix" ship and quietly do nothing more than once, so the object case gets its own section below.
Dedupe an array of primitives
The canonical form, and the one to reach for by default:
const ids = [4, 4, 8, 15, 16, 16, 23, 42, 42];
const unique = [...new Set(ids)]; // [4, 8, 15, 16, 23, 42]new Set(ids) constructs a set from the array (one entry per distinct value). The spread copies those entries into a fresh array in insertion order. It works for any mix of primitives:
const tags = ["js", "css", "js", "html", "css"];
[...new Set(tags)]; // ["js", "css", "html"]If you only need to count the distinct values or test membership, you do not need the spread back to an array at all. new Set(ids).size is the count, and set.has(x) is an O(1) membership check, far cheaper than array.includes(x) in a loop.
Set membership semantics: NaN and signed zero
Set decides whether two values are the same using the SameValueZero algorithm. It behaves exactly like === with one deliberate exception, and two cases matter for deduping:
NaNdeduplicates. This is the one place SameValueZero diverges from===. Under===,NaN === NaNisfalse, so a naiveincludes/indexOfdedupe leaves duplicateNaNs in. SameValueZero treats everyNaNas the same value, soSetcollapses them.+0and-0collapse to one entry. This is not a special case,+0 === -0is alreadytrue, so SameValueZero agrees and you get a single zero out. It is worth knowing becauseObject.is(SameValue) is the comparison that keeps the two zeros apart.
[...new Set([NaN, NaN])]; // [NaN] one entry
[...new Set([0, -0])]; // [0] one entryThis is the right behavior for almost every real dataset, and it is one more reason to prefer Set over a hand-rolled filter(indexOf) loop, which gets the NaN case wrong.
Dedupe an array of objects: the reference trap
This is the gotcha that sends people to the search box. Set compares objects by reference, so two object literals with identical contents are different values to it:
const users = [
{ id: 1, name: "Ada" },
{ id: 2, name: "Linus" },
{ id: 1, name: "Ada" },
];
[...new Set(users)].length; // 3 nothing was removedEach { id: 1, name: "Ada" } is a separate object in memory, so Set keeps all three. The fix is to dedupe by a key you actually care about. A Map keyed on that property is the cleanest way: building the map overwrites earlier entries with the same key, and .values() gives you back one object per key.
const byId = [...new Map(users.map((u) => [u.id, u])).values()];
// [ { id: 1, name: "Ada" }, { id: 2, name: "Linus" } ]Read it inside-out: users.map(u => [u.id, u]) builds [key, value] pairs, new Map(...) keeps only the last pair per key, and .values() spread back into an array gives the deduped result. Note this keeps the last object seen for a given id. To keep the first instead, reverse before building the map, or guard with if (!map.has(u.id)) map.set(u.id, u) in a loop.
If you need to dedupe by the whole object rather than one field, the usual trick is to key on a serialized form:
const seen = new Map(items.map((it) => [JSON.stringify(it), it]));
const unique = [...seen.values()];That works, but mind the caveat: JSON.stringify is key-order sensitive, so {a: 1, b: 2} and {b: 2, a: 1} serialize differently and will not dedupe against each other. It also drops undefined values and functions. For a small, fixed object shape it is fine; for arbitrary objects, pin down a canonical key (often a real id) instead of stringifying.
I wrote a small te-prefixed helper for the by-property case, since it comes up constantly:
const teUniqueBy = (arr, keyFn) =>
[...new Map(arr.map((item) => [keyFn(item), item])).values()];
teUniqueBy(users, (u) => u.id); // deduped by idThe ES5 fallback
Array.from(new Set(arr)) is exactly equivalent to [...new Set(arr)] and predates spread syntax. You only need it in environments without spread, which in practice means very old transpile targets:
var unique = Array.from(new Set(arr));In 2026 there is no reason to prefer it for compatibility (Set and Array.from both date to ES2015, and spread arrived with them). Reach for Array.from when you also want to map in the same pass, for example Array.from(new Set(arr), (x) => x * 2).
See also
- Modern JavaScript array methods:
at,flat,toReversed, and the mutation trap, the wider reference this dedupe pattern sits inside. - Merge two arrays in JavaScript: combine arrays with spread or
concat, then dedupe the result with theSettrick above. - JavaScript object methods:
keys,values,entries, andfromEntries, the toolkit behind keying objects for dedupe.
Sources
Authoritative references this article was fact-checked against.
- Set (MDN Web Docs)developer.mozilla.org
- Equality comparisons and sameness (MDN Web Docs)developer.mozilla.org
- Map (MDN Web Docs)developer.mozilla.org





