What is the difference between Array.prototype.includes and Array.prototype.indexOf, and why can indexOf never find NaN inside an array?
answer
- same search, different answer shape
- one returns a place, one a yes/no
- the value that is not equal to itself
- SameValueZero versus strict equality
- holes are skipped by only one of them
basics
~20 sArray.prototype.includes returns a boolean and compares with SameValueZero, so it finds NaN; indexOf returns a position or -1 and compares with strict equality, and NaN === NaN is false, so it never matches. They also disagree on holes in sparse arrays.
solid answer
~40 s`indexOf` answers *where* and `includes` answers *whether*: `indexOf` returns the first matching index or `-1`, `includes` returns `true` or `false`. The behavioural difference is the comparison algorithm. `indexOf` uses strict equality, the same rule as `===`, and `NaN === NaN` is `false` by IEEE-754, so `[NaN].indexOf(NaN)` is `-1`. `includes` uses **SameValueZero**, which is strict equality except that it treats `NaN` as equal to itself, so `[NaN].includes(NaN)` is `true`. Both still treat `+0` and `-0` as equal, so that is not a point of difference. They also handle sparse arrays differently: `indexOf` skips holes, while `includes` reads every index, so `Array(3).includes(undefined)` is `true` but `Array(3).indexOf(undefined)` is `-1`. Prefer `includes` for membership, and `indexOf` only when you actually need the position.
code
javascript · 10 linesconst values = [1, NaN, 3];
console.log(values.indexOf(NaN)); // -1
console.log(values.includes(NaN)); // true
const sparse = Array(3); // three holes
console.log(sparse.indexOf(undefined)); // -1 (holes are skipped)
console.log(sparse.includes(undefined)); // true (holes read as undefined)
// Both agree on signed zero
console.log([0].includes(-0), [-0].indexOf(0)); // true 0go deeper
Be able to say that includes gives a true/false answer and indexOf gives a position or -1, and that includes is the clearer choice when you only want to know whether a value is present.
Explain the comparison algorithms by name: strict equality for indexOf versus SameValueZero for includes, and why that makes NaN the one value they disagree on. Mention that both still treat +0 and -0 as equal.
Show where this bites real data: any array holding parsed or computed numbers can contain NaN, so an indexOf membership check silently reports absent. Be ready to talk about holes reading as undefined and about swapping repeated linear scans for a keyed lookup.
Frame it as a codebase convention: decide that membership checks return booleans, ban the indexOf(...) !== -1 idiom in review, and make sure the data pipeline rejects NaN at the boundary rather than leaving every downstream search to cope with it.
## Two questions, two return shapes `Array.prototype.indexOf(searchElement, fromIndex)` returns the index of the first element equal to `searchElement`, or `-1` if there is none. `Array.prototype.includes(searchElement, fromIndex)` returns a boolean. `includes` arrived in ES2016 largely because the older idiom `arr.indexOf(x) !== -1` is easy to write wrong — `-1` is truthy, so a bare `if (arr.indexOf(x))` is broken for both the miss case and a match at index `0`. Neither takes a predicate. Both compare a **value** you hand them, which is what separates them from `find` and `some`. ## The comparison algorithm is the real difference `indexOf` compares with the spec operation `IsStrictlyEqual` — exactly what `===` does. `includes` compares with **SameValueZero**. The two rules agree on everything except one value: - `NaN` is not strictly equal to anything, including itself. IEEE-754 defines it that way because `NaN` means "not a number", and no two unknowns are known to be equal. So `indexOf` can never match a `NaN` element. - SameValueZero is defined as "same value, but `+0` and `-0` count as the same". It reports `NaN` equal to `NaN`, so `includes` finds it. ```js const xs = [1, NaN, 3]; xs.indexOf(NaN); // -1 — strict equality, NaN === NaN is false xs.includes(NaN); // true — SameValueZero treats NaN as equal to itself ``` A common wrong answer is that `includes` also differs on `-0`. It does not: strict equality already reports `+0 === -0` as `true`, and SameValueZero deliberately keeps that behaviour (that is the "Zero" in the name). So `[0].includes(-0)` and `[-0].indexOf(0)` both report a match. If you need `+0` and `-0` to be distinguishable, neither of these methods will do it. Because `NaN` is the only divergence, the practical rule is: any array whose values can be the result of arithmetic — a parse, a division, a subtraction that hit a missing field — can contain `NaN`, and a membership check written with `indexOf` will silently claim it is absent. ## Sparse arrays: holes A *hole* is an index that was never assigned — produced by `Array(3)`, by `[1, , 3]`, or by raising `length`. Holes are not the value `undefined`; the index simply does not exist on the object. `indexOf` checks for the property's presence and skips holes entirely. `includes` reads every index from `fromIndex` to `length - 1` and a missing index reads as `undefined`. Hence: ```js const sparse = Array(3); // three holes, length 3 sparse.indexOf(undefined); // -1 — holes skipped sparse.includes(undefined); // true — holes read as undefined ``` This is the second divergence, and it is worth naming in an interview because it shows you know holes are a distinct state rather than a synonym for `undefined`. ## fromIndex behaves the same way Both accept an optional second argument. A negative `fromIndex` is added to `length`; if the result is still negative, the whole array is searched. `arr.includes(x, -2)` therefore checks only the last two elements. `indexOf` has an extra sibling, `lastIndexOf`, which scans backwards; `includes` has no such variant because direction cannot change a boolean. ## What neither of them does Both compare values, so for objects they compare **references**. Two structurally identical objects are different values, and `includes` reports `false`. To search by content you need a predicate — `some` for a boolean, `find` or `findIndex` for the element or its position. ```js [{ id: 7 }].includes({ id: 7 }); // false — different references [{ id: 7 }].some(o => o.id === 7); // true — content comparison ``` A subtler point about strings: these methods compare whole elements, not substrings. `['abcd'].includes('bc')` is `false`. `String.prototype.includes` is a different method that *does* do substring search, and confusing the two is a common slip. ## Choosing between them Use `includes` whenever the question is membership: it reads as the intent, returns a boolean you can use directly, has no `-1` trap, and handles `NaN`. Reach for `indexOf` only when the position is what you need next. If the array is large and the membership check runs repeatedly, both are linear scans, and the usual fix is to build a keyed lookup once instead of rescanning — but for a one-off check on a small array, `includes` is the clear default.
- If you must stay with `indexOf`, how would you locate a `NaN` element?You cannot with `indexOf` — no value is strictly equal to `NaN`. Use `findIndex` with a predicate that tests for it, for example `arr.findIndex(Number.isNaN)`, which returns the position. `Number.isNaN` is the reliable test because it does not coerce its argument, unlike the global `isNaN`. If you only need a yes/no, `includes(NaN)` already handles it.
- Does `includes` distinguish `+0` from `-0`?No. SameValueZero deliberately treats `+0` and `-0` as the same value, exactly as strict equality does, so `[0].includes(-0)` is `true`. That is the one place where SameValueZero and the stricter "same value" rule diverge. If you genuinely need to tell the two zeros apart, a value-comparison method is the wrong tool and you need an explicit test in a predicate.
- Why is `['abcd'].includes('bc')` false when `'abcd'.includes('bc')` is true?They are two different methods. `Array.prototype.includes` compares each **element** to the search value, and the element `'abcd'` is not the same string as `'bc'`. `String.prototype.includes` performs a substring search within one string. Mixing them up is a frequent source of a membership check that quietly never matches.
- How does the optional second argument change either method?Both take a `fromIndex` that sets where the scan starts. A negative value is added to `length`, and if it is still negative the whole array is searched, so `arr.includes(x, -2)` inspects only the last two elements. `indexOf` also has `lastIndexOf` for a backwards scan; `includes` has no reverse variant, since direction cannot change a boolean result.
saying these in an interview costs you the question
- Says indexOf finds NaN like any other number
- Claims includes and indexOf differ on -0
- Treats a hole as identical to the value undefined
- Uses if (arr.indexOf(x)) as a membership test
- Expects includes to match objects with equal fields
- Thinks array includes searches inside string elements