How can I check if a value exists in a JavaScript array?

I’m trying to figure out the most reliable and modern way to check if a specific value exists in a JavaScript array. I’ve seen different approaches like indexOf, includes, and some older patterns, and I’m confused about which one I should use in real-world code. I also need to know how this works with objects and not just simple values, since my array can contain complex data. Can someone explain the best practices and potential pitfalls?

Use includes. That is the modern and clear way.

Basic example:

const arr = [1, 2, 3];

arr.includes(2); // true
arr.includes(5); // false

For strings:

const fruits = [‘apple’, ‘banana’, ‘orange’];

fruits.includes(‘banana’); // true
fruits.includes(‘pear’); // false

Key points:

  1. Prefer includes over indexOf
    Old pattern:
    if (arr.indexOf(value) !== -1) { … }
    Problem: less readable, returns an index, not a boolean.

    New pattern:
    if (arr.includes(value)) { … }
    This reads like normal english and returns a boolean.

  2. Equality rules
    includes uses SameValueZero.
    That means:

    • It treats NaN as equal to NaN.
    • It treats 0 and -0 as equal.

    Example:

    const nums = [1, NaN];

    nums.includes(NaN); // true
    nums.indexOf(NaN); // -1 (fails here)

  3. Browser / environment support
    includes works in all modern browsers and Node.
    If you support reaaaally old browsers, add a polyfill or use indexOf.

  4. For objects or more complex checks
    includes checks by reference or primitive equality.
    It does not do deep comparison.

    Example:

    const arr = [{ id: 1 }, { id: 2 }];

    arr.includes({ id: 1 }); // false, different object reference

    Use some when you need a condition:

    arr.some(item => item.id === 1); // true

  5. Comparing the main options

    includes

    • Best for: “Does this exact value exist”
    • Returns boolean
    • Handles NaN

    indexOf

    • Older style
    • Returns index or -1
    • Fails with NaN

    some

    • Best for: “Does any item match this condition”
    • Works with objects

Quick cheatsheet:

// primitives
if (arr.includes(value)) {
// value exists
}

// objects or custom condition
if (arr.some(x => x.id === targetId)) {
// match exists
}

So for most use cases, use includes for simple value checks and some for more complex logic.

Use includes, like @sonhadordobosque said, but I’d actually split the answer into what kind of “exists” you really mean, because that’s where people get tripped up.

  1. Exact primitive value?
    Use includes or some, depending on how strict you want to be.
const arr = [1, 2, 3, NaN];

arr.includes(2);       // true
arr.includes(NaN);     // true

If you care about position too, then indexOf is still fine:

const i = arr.indexOf(2); // 1 or -1 if missing

Personally I still use indexOf when I actually need the index, not just existence. “Never use indexOf” is a bit overdramatic.

  1. More complex condition, even for primitives
    some is cleaner when the check is not just “is this value here”:
const nums = [1, 4, 9, 16];

// any square > 10?
nums.some(n => n > 10);  // true

You could abuse includes with precomputed values, but then your code becomes a puzzle for future-you.

  1. Objects: identity vs “same data”
    includes only checks reference identity:
const users = [{ id: 1 }, { id: 2 }];
users.includes({ id: 1 });  // false

If your “exists” means “same object in memory,” then includes is correct:

const u = { id: 1 };
const users2 = [u];
users2.includes(u);       // true

If you mean “same content,” then some or find:

users.some(u => u.id === 1);    // true

const found = users.find(u => u.id === 1); // returns the object or undefined
  1. Type-safety & readability
    Where I disagree a bit with the “always prefer includes” mindset:
    If you already need the index later, doing includes first and indexOf second is just double work:
// bad pattern
if (arr.includes(value)) {
  const i = arr.indexOf(value);
  // use i
}

Just do:

const i = arr.indexOf(value);
if (i !== -1) {
  // use i directly
}

That’s more efficient and honestly still readable.

  1. Weird edge cases
    If you’re in a codebase that relies on Object.is semantics or very specific equality rules, includes might be too magical. In those cases, sticking to some with an explicit condition is clearer:
arr.some(x => Object.is(x, value));
  1. Practical rule of thumb
  • Primitive check only, no index needed: includes
  • Need index: indexOf
  • Need a condition or object property check: some / find
  • Need to return all matches: filter

The confusing part isn’t which method is “modern,” it’s deciding what “exists” actually means in your situation. Once you define that, the right method pretty much picks itself.

Use what @himmelsjager and @sonhadordobosque said as the baseline, but think in terms of layers of intent rather than “which function is most modern.”

1. Decide what “exists” actually means

  • Exact value, position irrelevant
  • Exact value, position needed
  • Value that matches a condition
  • Value that should be kept in sync with another structure (like a Set or Map)

They covered the first three pretty thoroughly with includes, indexOf, some, and find, so I’ll focus on the last one and the tradeoffs.


2. When an array is the wrong tool

If your main operation is “does this value exist” and you do it a lot, arrays are actually the noisy solution. A Set is often cleaner and more explicit:

const ids = new Set([1, 2, 3]);

ids.has(2);  // true
ids.has(5);  // false

Compared with arr.includes(2), the intent with ids.has is sharper: this is a membership check, not a positional data structure. For long‑lived collections, that reads better than sprinkling includes everywhere.

Cons of switching to Set:

  • No direct indexing like arr[0].
  • Order is preserved but random access by index is awkward.
  • Some array utilities (map, filter) require conversion or extra steps.

Pros:

  • Membership checks are conceptually first‑class (has).
  • No duplicates unless you explicitly want them.
  • For large datasets, performance characteristics are usually better than scanning with includes.

For key/value data, Map + has is the same idea:

const usersById = new Map([
  [1, { id: 1 }],
  [2, { id: 2 }]
]);

usersById.has(1);  // true

3. Avoid “double work” patterns

Here I disagree slightly with the heavy “always use includes” vibe. A really common anti‑pattern:

if (arr.includes(value)) {
  const index = arr.indexOf(value);
  // use index
}

That scans the array twice. If you need the index, start with indexOf and interpret -1 as “does not exist.” That is still perfectly idiomatic modern JS and more efficient.


4. Consider structural equality explicitly

Both includes and indexOf rely on equality rules that are not obvious to everyone. If your definition of “exists” is conceptual rather than reference or SameValueZero, be explicit:

function existsUserById(users, id) {
  return users.some(u => u.id === id);
}

Wrapping some or find in a small helper like this makes the calling code read better than repeating raw some checks everywhere.


5. Summary mental model

  • If your data is inherently “a bag of items”: use arrays with includes / some.
  • If your data is inherently “a set of unique items”: consider Set.has.
  • If you are constantly mapping from key to value: consider Map.has.
  • If you need both membership and index, keep using indexOf instead of doing includes + indexOf.

Both @himmelsjager and @sonhadordobosque are right to push you away from the old indexOf(...) !== -1 pattern when you only need a boolean. Just do not be afraid to step outside arrays entirely when the real problem is “I’m tracking membership,” not “I’m managing an ordered list.”