Question

How to Remove Duplicates from a JavaScript Array

javascriptuniquearrays

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
  • ""
  • false
  • null
  • undefined
  • NaN

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:

  • 0 for counts, prices, or IDs
  • "" for empty input
  • false for 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.

Take Quiz

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 array
  • 0 is kept correctly because Set does 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 = 0
  • e = arr[0] → 5
  • 5 is truthy, so the loop runs
  • Output: value: 5

Next iteration

  • i = 1
  • e = arr[1] → 2
  • 2 is truthy, so the loop runs
  • Output: value: 2

Next iteration

  • i = 2
  • e = arr[2] → 0
  • 0 is falsy, so the loop stops
  • 9 is 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 → prints 5
  • i = 1 → prints 2
  • i = 2 → prints 0
  • i = 3 → prints 9
  • i = 4 → loop ends because 4 < arr.length is 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.

Take Quiz

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:

  • 0
  • false
  • null
  • undefined
  • NaN

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

ApproachExampleHandles 0 correctlyKeeps original typesReadabilityNotes
Set[...new Set(arr)]YesYesHighBest modern default
filter + indexOfarr.filter((v, i) => arr.indexOf(v) === i)YesYesMediumCommon older approach
Object lookupo[arr[i]] = trueYes, if loop is correctNot alwaysLowKeys become strings
Broken truthy loopfor (; e = arr[i]; i++)NoNoLowStops 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.

Take Quiz

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
  • ""
  • false
  • null
  • undefined
  • NaN

Alternative deduplication

const unique = array.filter((value, index, arr) => arr.indexOf(value) === index);

Best practice

  • Prefer Set for simple deduplication
  • Check array bounds, not value truthiness
  • Avoid extending Array.prototype in 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.

Take Quiz

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

  1. Create a function named getUniqueProductIds.
  2. Accept an array of numbers as input.
  3. Return a new array with duplicates removed.
  4. Preserve valid falsy values such as 0.
  5. Log the result for a sample input array.
Take Quiz

Keep learning

Related questions

View more