JavaScript example

How to remove duplicates from an array in JavaScript

6 min read Updated Sep 2026 Runs in an isolated runtime
Quick answer

Pass the array through a Set and spread it back out: [...new Set(arr)]. A Set holds each value once and remembers insertion order, so you get the first occurrence of every value in its original position. For objects, dedupe on a key with a Map: [...new Map(arr.map((o) => [o.id, o])).values()].

JavaScript arrays have no unique() method, but the language has had a Set since ES2015, and that turns deduplication into a one-liner for strings, numbers and other primitives. The work starts when "duplicate" means something looser than ===: two objects with the same id, or Go and go. Those need a key you choose yourself, which is what sections 3 and 4 cover. Section 5 merges two arrays and dedupes them in one go. Each example runs on this page: hit Run, then edit the code and run it again.

1[...new Set(arr)]Recommended

new Set(iterable) adds every element and silently ignores the ones it already has. Spreading it back into [...] (or passing it to Array.from) turns it into an array again. Sets iterate in insertion order, so the result keeps the first occurrence of each value where it was. It is a single O(n) pass and the original array is not touched.

set.js

Output

Prints [ 'js', 'go', 'rust' ], 6 -> 3 and [ 3, 1, 2 ]: first-seen order, not sorted. The fourth line, [ NaN, 1, '1', 0 ], shows the equality rule Sets use (SameValueZero): it is === except that NaN matches itself, so the two NaNs and the 0/-0 pair collapse while the number 1 and the string '1' stay apart. The last line is the limit: two lookalike objects are still two different references, so [ { id: 1 }, { id: 1 } ] survives intact. Section 3 fixes that.

2filter() + indexOf() (the pre-Set idiom)

Before ES2015 the standard answer was to keep an element only if the index you are looking at is the first index it appears at. It still works everywhere and you will meet it in older code, but indexOf scans from the start for every element, so it is O(n²): fine for a few hundred items, noticeably slow on tens of thousands. Flipping the test to !== is a handy way to see which values were repeated.

filter-indexof.js

Output

The dedupe prints the same [ 'js', 'go', 'rust' ] as the Set version, and the flipped test prints [ 'js', 'go', 'js' ]: every extra copy, so a value repeated three times shows up twice. The last two lines are the bug to know about. indexOf uses strict equality, and NaN === NaN is false, so it returns -1 for NaN, the test never passes, and [ 1, 2 ] has lost the value entirely. The Set keeps one: [ 1, NaN, 2 ]. Prefer new Set for new code; reach for this form only when you need the index, for example to keep the last occurrence with lastIndexOf.

3Dedupe an array of objects by a key

A Set compares objects by reference, so two records with the same id are not duplicates to it. What you want is to pick the field that makes two records "the same" and dedupe on that. There are two idioms, and they differ in which record survives. A Map built from [id, object] pairs keeps the last record for each id (a later set overwrites the value), while a Set of seen ids inside filter keeps the first.

objects-by-key.js

Output

Both print three users in the order 1, 2, 3, but the Map version starts with { id: 1, name: 'Ada L.' } and the filter version with { id: 1, name: 'Ada' }. Node prints each array over several lines because the objects no longer fit on one. The Map keeps id 1 at the front because overwriting a key's value doesn't move it. Pick the one that matches your data: last-wins is right when later rows are updates, first-wins when the first record is the canonical one. To dedupe on several fields, build a composite key such as `${u.first}|${u.last}`. Avoid JSON.stringify(obj) as a key: it depends on property order, so { a: 1, b: 2 } and { b: 2, a: 1 } come out different.

4Case-insensitive dedupe

The same idea handles JavaScript, javascript and JAVASCRIPT: record a normalised key in the seen-set, but push the original string. That keeps the spelling of the first occurrence. If you are happy for every value to come out lowercased, normalise first and hand the result to a Set.

case-insensitive.js

Output

Prints [ 'JavaScript', 'go', 'Rust' ], the first spelling of each tag, then [ 'javascript', 'go', 'rust' ]. The key function is the only part that changes between variations: add .trim() to ignore surrounding spaces, or use tag.normalize('NFC').toLowerCase() when the input may mix composed and decomposed accents. toLowerCase is enough for English tags; for user text in other languages, localeCompare with { sensitivity: 'base' } is the stricter comparison, though it doesn't give you a key you can put in a Set.

5Merge two arrays and remove duplicates

Merging without duplicates is the same operation with two inputs: spread both arrays into one Set. ES2025 also added set algebra to Set.prototype, so a.union(b) reads exactly like what it does. union, intersection and difference all return a new Set and are available in Node 22+ and every current browser. The pre-ES2015 version is concat followed by the filter from section 2.

merge.js

Output

Every merge prints [ 'js', 'go', 'rust', 'zig', 'odin' ]: all of a in order, then the values from b that were new. Logging the Set itself shows Node's Set format, Set(5) { 'js', 'go', 'rust', 'zig', 'odin' }, which is why you spread it before handing it to code that expects an array. The intersection prints [ 'js', 'go' ] and the difference [ 'zig', 'odin' ], the values only b has. The argument to union has to be Set-like (it needs size and has): pass a plain array and it throws TypeError: The .size property is NaN, so wrap it in new Set() first. For arrays of objects, merge with [...a, ...b] and run the Map-by-key dedupe from section 3.

6Which should you use?

MethodOrderTimeBest for
[...new Set(arr)]First-seenO(n)Strings, numbers, any primitives
arr.filter((x, i) => arr.indexOf(x) === i)First-seenO(n²)Legacy code; drops NaN
[...new Map(arr.map((o) => [o.id, o])).values()]First-seen keyO(n)Objects by id, last record wins
seen Set + filter on key(x)First-seenO(n)Objects (first wins), ignoring case
new Set(a).union(new Set(b))a, then new bO(n)Merging two arrays (ES2025)

Frequently asked questions

How do I get all unique values in a JavaScript array?

Spread the array through a Set: const unique = [...new Set(arr)]; (or Array.from(new Set(arr))). It keeps the first occurrence of each value in its original order, runs in one pass, and leaves the original array unchanged. It works for primitives; for objects, dedupe on a key with a Map.

Does new Set() keep the original order of the array?

Yes. A Set iterates in insertion order, and adding a value it already holds does nothing, so [...new Set(arr)] returns each value at the position of its first appearance. [...new Set([3, 1, 3, 2, 1])] gives [ 3, 1, 2 ], not a sorted array. Call .sort() on the result if you want it sorted.

Why does new Set() not remove duplicate objects?

A Set compares objects by reference, so two separate objects with identical contents are two different values. Pick the field that identifies a record and dedupe on it: [...new Map(arr.map((o) => [o.id, o])).values()] keeps the last object per id, and a seen Set of ids inside filter keeps the first.

How do I merge two arrays and remove duplicates?

Spread both into one Set and back: [...new Set([...a, ...b])]. On Node 22+ and current browsers you can also write [...new Set(a).union(new Set(b))] with the ES2025 Set methods. Both keep the order of a followed by the new values from b. For arrays of objects, concatenate and then dedupe by key with a Map.

How do I find the duplicate values in an array instead of removing them?

Flip the filter test: arr.filter((x, i) => arr.indexOf(x) !== i) returns every extra copy, so a value that appears three times shows up twice. Wrap it in a Set, [...new Set(arr.filter((x, i) => arr.indexOf(x) !== i))], to list each duplicated value once.

Is filter with indexOf slower than a Set for removing duplicates?

Yes, on anything but small arrays. indexOf scans from the start for every element, which makes filter + indexOf O(n²), while building a Set is O(n). It also drops every NaN, because indexOf uses strict equality and NaN === NaN is false. Use it only for compatibility with very old engines.

Run it yourself

Open any of these in the full JavaScript editor — tweak, run, and share.

JavaScript playground