Question
I have an array of numbers, and I want to return only the unique values.
I found this code online, and it seems to work until the array contains 0. I also found a very similar version on Stack Overflow that does not fail.
How can I understand why this version breaks?
Array.prototype.getUnique = function () {
var o = {}, a = [], i, e;
for (i = 0; (e = this[i]); i++) {
o[e] = 1;
}
for (e in o) {
a.push(e);
}
return a;
};
In particular, why does the loop stop working when the array contains 0, and what is the correct modern JavaScript way to remove duplicates from an array?
Short Answer
By the end of this page, you will understand how to remove duplicate values from a JavaScript array, why some older looping patterns fail when values like 0 appear, and which modern approaches are safest and easiest to use. You will also learn how truthy and falsy values affect loop conditions and how to choose the right deduplication technique in real code.
Concept
The main concept behind this question is array deduplication and the related idea of truthy and falsy values in JavaScript.
The bug in the original code is not really about duplicates. It is about this loop condition:
for (i = 0; (e = this[i]); i++)
This code assigns this[i] to e, and then uses the assigned value as the loop condition. That means the loop continues only while e is truthy.
In JavaScript, these are examples of falsy values:
0""falsenullundefinedNaN
So if the array contains 0, then e = this[i] becomes 0, which is falsy, and the loop stops early.
Why this matters
In real programming, data often includes valid falsy values:
0for counts, prices, or IDs""for empty inputfalsefor flags
If your loop treats those values as a signal to stop, your program will behave incorrectly.
The deduplication part
The original code uses an object as a lookup table:
o[e] = 1;
That works in some cases, but it has limitations:
- Object keys are strings, so numbers become strings
- Some values may collide unexpectedly
- It is not the clearest modern approach
Today, the most common way to remove duplicates is with Set, because a Set stores only unique values.
Mental Model
Think of a for loop condition like a security guard checking whether work should continue.
In the broken version, the guard says:
"Keep going only if the current value looks truthy."
That sounds okay until the value is 0. Even though 0 is a perfectly valid array element, the guard treats it like a stop signal.
A better mental model is:
"Keep going while the index is still inside the array."
That means you should check the position in the array, not the value stored there.
So this is fragile:
for (i = 0; (e = this[i]); i++)
And this is reliable:
for (i = 0; i < this.length; i++)
The first asks, "Is this value truthy?" The second asks, "Is this index valid?"
That is the key difference.
Syntax and Examples
Modern way: use Set
const numbers = [1, 2, 2, 3, 0, 0, 4];
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // [1, 2, 3, 0, 4]
Why this works
new Set(numbers)creates a set of unique values...spreads the set back into an array0is kept correctly becauseSetdoes not depend on truthiness
Older but valid approach: use filter
const numbers = [1, 2, 2, 3, 0, 0, 4];
const uniqueNumbers = numbers.filter((value, index, array) => {
return array.indexOf(value) === index;
});
console.log(uniqueNumbers); // [1, 2, 3, 0, 4]
How it works
For each value, indexOf(value) returns the first position where that value appears. If the current index matches that first position, the value is kept.
Fixing the original style of loop
Array.prototype.getUnique = function () {
var o = {}, a = [], i, e;
for (i = 0; i < this.length; i++) {
e = this[i];
o[e] = 1;
}
for (e in o) {
a.push(Number(e));
}
return a;
};
console.log([1, 2, 2, 0, 3, 0].getUnique()); // [0, 1, 2, 3] or similar order
Important note
This version no longer stops at 0, because the loop checks i < this.length instead of checking the value itself.
However, Set is still the better modern solution.
Step by Step Execution
Consider this broken example:
const arr = [5, 2, 0, 9];
for (let i = 0, e; (e = arr[i]); i++) {
console.log("value:", e);
}
Step-by-step trace
Start
i = 0e = arr[0]→55is truthy, so the loop runs- Output:
value: 5
Next iteration
i = 1e = arr[1]→22is truthy, so the loop runs- Output:
value: 2
Next iteration
i = 2e = arr[2]→00is falsy, so the loop stops9is never reached
Correct version
const arr = [5, 2, 0, 9];
for (let i = 0; i < arr.length; i++) {
const e = arr[i];
console.log("value:", e);
}
Trace of the correct version
i = 0→ prints5i = 1→ prints2i = 2→ prints0i = 3→ prints9i = 4→ loop ends because4 < arr.lengthis false
This version works because the loop ends based on the array length, not the truthiness of the value.
Real World Use Cases
Removing duplicates from arrays is common in real applications.
Common scenarios
- User IDs: remove repeated IDs before querying a database
- Tags or categories: keep only unique labels from user input
- API results: merge data from multiple endpoints without duplicates
- Analytics: count unique page paths, events, or product IDs
- Form processing: clean repeated values from checkbox submissions
- Configuration lists: avoid duplicate feature flags or allowed roles
Example: unique tags
const tags = ["js", "css", "js", "html", "css"];
const uniqueTags = [...new Set(tags)];
console.log(uniqueTags); // ["js", "css", "html"]
Example: unique numeric IDs
const ids = [101, 203, 101, 404, 0, 404];
const uniqueIds = [...new Set(ids)];
console.log(uniqueIds); // [101, 203, 404, 0]
Notice that 0 is preserved correctly.
Real Codebase Usage
In real codebases, developers usually avoid extending built-in prototypes unless there is a strong reason. So instead of this:
Array.prototype.getUnique = function () {
// ...
};
it is more common to write a reusable utility function:
function getUniqueValues(array) {
return [...new Set(array)];
}
Why utility functions are preferred
- They are easier to test
- They do not affect all arrays globally
- They reduce the risk of naming conflicts
- They are easier for teams to read and maintain
Common patterns in real projects
Validation before deduplication
function getUniqueValues(array) {
if (!Array.isArray(array)) {
throw new TypeError("Expected an array");
}
return [...new Set(array)];
}
Early return for empty arrays
function getUniqueValues(array) {
if (array.length === 0) {
return [];
}
return [...new Set(array)];
}
Normalizing data first
Sometimes developers transform values before deduplicating.
const emails = [" A@EXAMPLE.COM ", "a@example.com", "b@example.com"];
const uniqueEmails = [
...new Set(emails.map(email => email.trim().toLowerCase()))
];
console.log(uniqueEmails); // ["a@example.com", "b@example.com"]
This pattern is very common in data cleaning.
Common Mistakes
1. Using the array value as the loop condition
Broken code:
for (let i = 0, e; (e = arr[i]); i++) {
console.log(e);
}
Why it fails
The loop stops at falsy values like 0, false, or "".
Fix
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
2. Forgetting that object keys become strings
Broken expectation:
const o = {};
o[1] = true;
o[2] = true;
console.log(Object.keys(o)); // ["1", "2"]
Why it matters
If you use an object for deduplication, numeric values come back as strings unless you convert them.
3. Extending Array.prototype unnecessarily
Array.prototype.getUnique = function () {
return [...new Set(this)];
};
Why to avoid it
- It changes behavior globally
- It may conflict with other code
- It can make debugging harder
Prefer:
function getUniqueValues(array) {
return [...new Set(array)];
}
4. Assuming all deduplication methods preserve the same behavior
Different techniques may differ in:
- output order n- type handling
- performance
- readability
For example, object-based deduplication may not behave like Set.
5. Ignoring special values
Arrays may contain values like:
0falsenullundefinedNaN
A correct deduplication approach should handle them intentionally.
const values = [0, 0, false, false, NaN, NaN];
console.log([...new Set(values)]); // [0, false, NaN]
Comparisons
| Approach | Example | Handles 0 correctly | Keeps original types | Readability | Notes |
|---|---|---|---|---|---|
Set | [...new Set(arr)] | Yes | Yes | High | Best modern default |
filter + indexOf | arr.filter((v, i) => arr.indexOf(v) === i) | Yes | Yes | Medium | Common older approach |
| Object lookup | o[arr[i]] = true | Yes, if loop is correct | Not always | Low | Keys become strings |
| Broken truthy loop | for (; e = arr[i]; i++) | No | No | Low | Stops on falsy values |
Set vs object lookup
const arr = [1, 2, 2, 0];
console.log([...new Set(arr)]); // [1, 2, 0]
Compared with object lookup:
const obj = {};
for (let i = 0; i < arr.length; i++) {
obj[arr[i]] = true;
}
console.log(Object.keys(obj)); // ["0", "1", "2"]
The object version turns numbers into strings. Set does not.
for loop vs for...of
If you need manual iteration, for...of is often cleaner:
for (const value of arr) {
console.log(value);
}
This also avoids the accidental truthy/falsy loop condition problem.
Cheat Sheet
Remove duplicates
const unique = [...new Set(array)];
Safe classic loop
for (let i = 0; i < array.length; i++) {
const value = array[i];
}
Avoid this pattern
for (let i = 0, value; (value = array[i]); i++) {
// breaks on 0, false, "", null, undefined, NaN
}
Falsy values in JavaScript
0""falsenullundefinedNaN
Alternative deduplication
const unique = array.filter((value, index, arr) => arr.indexOf(value) === index);
Best practice
- Prefer
Setfor simple deduplication - Check array bounds, not value truthiness
- Avoid extending
Array.prototypein app code - Use utility functions for reuse
Utility function
function uniqueValues(array) {
return [...new Set(array)];
}
FAQ
Why does 0 break the original loop?
Because 0 is a falsy value in JavaScript. The loop uses the assigned value itself as the condition, so it stops when the value is 0.
What is the easiest way to remove duplicates from a JavaScript array?
Use Set:
const unique = [...new Set(array)];
Is Set better than using an object?
Usually yes. Set is clearer, preserves types, and is designed specifically for unique values.
Why does the object-based method return strings?
Object keys are stored as strings, so numeric values like 1 and 2 come back as "1" and "2".
Should I add getUnique to Array.prototype?
Usually no. It is safer and more maintainable to use a normal function instead.
Does filter also work for removing duplicates?
Yes. A common version is:
array.filter((value, index, arr) => arr.indexOf(value) === index)
Does Set handle NaN correctly?
Yes. Set treats repeated NaN values as duplicates and keeps only one of them.
Mini Project
Description
Build a small utility that cleans a list of product IDs by removing duplicates. This demonstrates safe array iteration, handling values like 0, and using the modern Set approach in a practical scenario.
Goal
Create a function that accepts an array of product IDs and returns a new array containing each ID only once, while preserving valid values such as 0.
Requirements
- Create a function named
getUniqueProductIds. - Accept an array of numbers as input.
- Return a new array with duplicates removed.
- Preserve valid falsy values such as
0. - Log the result for a sample input array.
Keep learning
Related questions
Abort Ajax Requests with jQuery jqXHR.abort()
Learn how to cancel an in-progress jQuery Ajax request with jqXHR.abort(), handle abort status safely, and avoid stale UI updates.
Access the Correct this Inside a JavaScript Callback
Learn why JavaScript this changes in callbacks and how to preserve an object context using bind, arrow functions, and event handler patterns.
Add Key-Value Pairs to JavaScript Objects
Learn how to add key-value pairs to JavaScript objects with dot and bracket notation, dynamic keys, examples, and common mistakes.