To get all unique values in a JavaScript array, here’s a detailed guide with examples, edge cases, and explanations for different scenarios:
1. Using Set
(ES6+)
Set
is a built-in object that stores unique values, making it the most efficient and modern approach.
Example:
const arr = [1, 2, 2, "hello", "hello", NaN, NaN, {a: 1}, {a: 1}];
const unique = [...new Set(arr)];
console.log(unique);
// [1, 2, "hello", NaN, {a: 1}, {a: 1}]
Key Points:
- Handles
NaN
: Treats allNaN
values as identical (unlike other methods). - Objects: Treats objects as unique unless they are the same reference.
- Order: Preserves the order of the first occurrence.
Edge Cases:
- Mixed types (e.g.,
1
vs"1"
) are treated as distinct. NaN
is deduplicated, but objects with identical properties are not.
2. Using filter
+ indexOf
Filters elements based on their first occurrence in the array.
Example:
const arr = [1, 2, 2, "hello", "hello", NaN, NaN];
const unique = arr.filter((item, index) => arr.indexOf(item) === index);
console.log(unique);
// [1, 2, "hello", NaN, NaN] ❌ Fails to deduplicate NaN!
Key Points:
- Fails for
NaN
:indexOf(NaN)
returns-1
, soNaN
values are not deduplicated. - Objects: Works only if objects are the same reference.
3. Using reduce
Accumulates unique values by checking against an array.
Example:
const arr = [1, 2, 2, "hello", "hello", NaN, NaN];
const unique = arr.reduce((acc, item) => {
return acc.includes(item) ? acc : [...acc, item];
}, []);
console.log(unique);
// [1, 2, "hello", NaN] ✅ Handles NaN!
Key Points:
- Handles
NaN
:includes()
correctly detectsNaN
. - Objects: Treats objects as unique unless they are the same reference.
- Performance: Inefficient for large arrays due to
O(n²)
time complexity.
4. Using an Object (Primitives Only)
Uses an object to track seen values (keys are coerced to strings).
Example:
const arr = [1, 2, 2, "1", NaN, NaN, {a: 1}];
const seen = {};
const unique = arr.filter(item => {
const key = typeof item + JSON.stringify(item); // Handle edge cases
return seen.hasOwnProperty(key) ? false : (seen[key] = true);
});
console.log(unique);
// [1, 2, "1", NaN, {a: 1}] ✅ Deduplicates NaN but treats 1 and "1" as unique!
Key Points:
- Primitives Only: Coerces keys to strings (e.g.,
1
and"1"
become"number:1"
vs"string:\"1\""
). - Custom Key Logic: Use
typeof
andJSON.stringify
to avoid collisions (e.g.,1
vs"1"
). - Fails for Objects: Generic objects are treated as unique even with identical properties.
5. Advanced: Deduplicate Objects by Property
To deduplicate objects based on a specific property (e.g., id
):
Example:
const users = [
{id: 1, name: "Alice"},
{id: 1, name: "Alice"},
{id: 2, name: "Bob"}
];
const seen = new Map();
const uniqueUsers = users.filter((user) => {
return seen.has(user.id) ? false : seen.set(user.id, true);
});
console.log(uniqueUsers);
// [{id: 1, name: "Alice"}, {id: 2, name: "Bob"}]
Key Points:
- Uses
Map
to track unique identifiers (e.g.,id
). - Works for objects with a unique key.
6. Handling Mixed Data Types
Example:
const mixedArr = [1, "1", true, "true", null, undefined, NaN, NaN];
const unique = [...new Set(mixedArr)];
console.log(unique);
// [1, "1", true, "true", null, undefined, NaN]
Key Points:
Set
treats all values strictly (1 !== "1"
).
Performance Comparison
Method | Time Complexity | Handles NaN ? | Handles Objects? |
---|---|---|---|
Set | O(n) | ✅ | ✅ (by reference) |
filter + indexOf | O(n²) | ❌ | ✅ (by reference) |
reduce | O(n²) | ✅ | ✅ (by reference) |
Object Tracking | O(n) | ✅ | ❌ |
Final Recommendations
- Modern Browsers: Use
Set
for simplicity and speed. - Legacy Code: Use
reduce
orfilter
+indexOf
(withNaN
checks). - Objects by Property: Combine
Map
with a unique identifier. - Mixed Data Types: Always test with edge cases like
1
vs"1"
orNaN
.
Example for Legacy Browsers:
function getUnique(arr) {
const seen = {};
return arr.filter((item) => {
const key = typeof item + JSON.stringify(item);
return seen.hasOwnProperty(key) ? false : (seen[key] = true);
});
}
This handles most primitives but not objects with identical properties.