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:
-
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. -
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) -
Browser / environment support
includes works in all modern browsers and Node.
If you support reaaaally old browsers, add a polyfill or use indexOf. -
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
-
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.
- Exact primitive value?
Useincludesorsome, 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.
- More complex condition, even for primitives
someis 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.
- Objects: identity vs “same data”
includesonly 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
- Type-safety & readability
Where I disagree a bit with the “always prefer includes” mindset:
If you already need the index later, doingincludesfirst andindexOfsecond 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.
- Weird edge cases
If you’re in a codebase that relies onObject.issemantics or very specific equality rules,includesmight be too magical. In those cases, sticking tosomewith an explicit condition is clearer:
arr.some(x => Object.is(x, value));
- 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
indexOfinstead of doingincludes+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.”