Get Unique Items from an Array
Deduping an array sounds trivial until objects, NaN, and case-insensitive strings show up. This snippet walks from the one-liner everyone reaches for first, to a key-projecting variant that handles object identity, to the gotcha around NaN that catches even seasoned engineers. Pick the version that matches your data shape and keep the rest as reference.
468 views
12
function unique(array) {
return [...new Set(array)];
}
console.log(unique([1, 2, 2, 3, 3, 3]));
// [1, 2, 3]
console.log(unique(['a', 'b', 'a', 'c']));
// ['a', 'b', 'c']Spreading an array into a Set and back into an array is the canonical JavaScript dedupe. Set uses the SameValueZero algorithm for membership, so it correctly treats NaN as equal to itself (unlike ===). It also preserves insertion order, which means the first occurrence of each value wins. Time complexity is O(n) average, but only for primitives. The next accordion shows why this breaks for objects.
function uniqueBy(array, keyFn) {
const seen = new Map();
for (const item of array) {
const key = keyFn(item);
if (!seen.has(key)) seen.set(key, item);
}
return [...seen.values()];
}
const users = [
{ id: 1, name: 'Ada' },
{ id: 2, name: 'Lin' },
{ id: 1, name: 'Ada (dupe)' },
];
console.log(uniqueBy(users, (u) => u.id));
// [{ id: 1, name: 'Ada' }, { id: 2, name: 'Lin' }]new Set(arrayOfObjects) deduplicates by reference identity, which means two distinct objects with identical fields stay in the result. The fix is to project each item to a primitive key (an id, a normalised string, a tuple) and dedupe on that. Storing the first item per key in a Map keeps the original object intact for downstream code. Use this whenever your equality is structural rather than identity, for example deduping API rows, normalising form input, or merging events.
function uniqueCaseInsensitive(array) {
return [...new Map(array.map((s) => [s.toLowerCase(), s])).values()];
}
console.log(uniqueCaseInsensitive(['Apple', 'apple', 'BANANA', 'banana']));
// ['apple', 'banana']When the difference between 'Apple' and 'apple' is just casing, the dedupe key needs to be normalised before comparison. Building a Map from [normalisedKey, originalValue] pairs keeps the last-seen casing, which is usually what users expect after a paste from a CSV. Swap toLowerCase() for normalize('NFD') if you also need to fold accents (see the js-string-strip-accents snippet). The trade-off is one extra pass over the input, still O(n) time.
