indexOf()

ES5+

Returns the first index at which a given element can be found in the array.

Syntax

array.indexOf(searchElement, fromIndex)

Parameters

searchElement any

Element to locate in the array

Return Value

number

The index of the element, or -1 if not found

Examples

JavaScript
const fruits = ['apple', 'banana', 'cherry'];
console.log(fruits.indexOf('banana'));
console.log(fruits.indexOf('mango'));
Output:
// 1 -1

📌 When to Use

Use indexOf() when you have a primitive value in hand and need to know where it sits in the array - typically as the prelude to a positional operation such as splice() removal, replacing the element at that slot, or comparing relative order of two entries. Because it matches by strict equality with no callback, it is the leanest position lookup available: no function allocation, no per-element JavaScript call. The decision tree around it is short. If you only need existence, includes() says so directly and also handles NaN. If matching requires any logic beyond === - an object property, case folding, a tolerance range - the value cannot be found by identity and findIndex() with a predicate is the tool. If you need the last occurrence rather than the first, lastIndexOf() scans from the tail. And when the same array is searched by value over and over, precompute a Map from value to index and retire the linear scans altogether. The optional fromIndex parameter is worth knowing: it resumes the search after a previous hit, which is how you enumerate all occurrences of a value in one forward pass without slicing copies.

⚠️ Common Mistakes

Treating the returned index as a boolean: if (arr.indexOf(x)) misfires twice over, since a match at position 0 is falsy ("found" reads as "missing") and the -1 miss sentinel is truthy ("missing" reads as "found"). Both branches are inverted for the two most important cases. Always compare explicitly with !== -1, or use includes() when existence is the real question.

Searching for NaN. indexOf() is specified to use strict equality, and NaN === NaN is false, so [NaN].indexOf(NaN) returns -1 even though the value is plainly there. Arrays fed by failed numeric parsing accumulate NaN entries that indexOf() can never locate - use includes() for detection or findIndex(Number.isNaN) for the position.

Expecting object matching by content. indexOf() compares references, so it only finds an object when you pass the very same instance that lives in the array - useful when you already hold that reference, useless for locating "an equivalent object". For property-based position lookup, findIndex(o => o.id === target.id) is required.

Removing all copies of a value with a single indexOf() + splice() pair. That deletes only the first occurrence; duplicates survive. Loop while (i = arr.indexOf(v)) !== -1 to remove them all in place, or simply build a clean copy with filter(x => x !== v), which is clearer and immune to index-shifting mistakes.

Mixing up string and number ids before searching: ["1","2"].indexOf(2) is -1 because strict equality never coerces. Route parameters, dataset attributes, and form values are all strings, so normalize the probe value first - indexOf(String(id)) - or the lookup fails only for user-driven code paths.

✅ Best Practices

Reserve indexOf() for when the position will actually be used; the moment the index only feeds a !== -1 comparison, switch to includes(). This split keeps each call site self-documenting: indexOf() promises positional work follows, includes() promises a pure yes/no gate.

Compare with !== -1 and nothing else. The variants > 0 and >= 1 silently exclude index 0, and ~indexOf(x) (the old bitwise-NOT trick that maps -1 to 0) is clever but unreadable to most maintainers. The explicit comparison costs nothing and cannot be misread.

Use lastIndexOf() when the final occurrence matters - for example, finding the most recent entry of a value in an append-only log - and note that comparing indexOf(x) with lastIndexOf(x) is a compact duplicate detector: they differ exactly when the value appears more than once.

Enumerate every occurrence with the fromIndex parameter instead of repeated slicing: advance the start position past each hit in a while loop. One forward pass, no copies, and the indices arrive already in ascending order for later splicing from the back.

When indices must stay valid across removals, delete from the highest index to the lowest. Splicing at position 3 shifts everything after it, so a previously found index 7 now points at the wrong element - processing indices in descending order sidesteps the shift entirely.

⚡ Performance Notes

indexOf() runs a native strict-equality scan - O(n) worst case with an extremely small constant factor, since no user callback is invoked per element. In V8 it is among the fastest possible array searches, and on packed arrays of Smis (small integers) the engine can use specialized fast paths that make it quicker than an equivalent hand-written loop in many cases. That speed still cannot beat asymptotics: resolving values to positions inside a loop over another collection multiplies into O(n*m), and the fix is a one-time Map from value to index. The fromIndex parameter trims re-scanning when enumerating multiple occurrences, turning what would be repeated full scans into a single cumulative pass. One spec nuance: on sparse arrays indexOf() skips holes rather than treating them as undefined - [ ,undefined].indexOf(undefined) is 1, not 0 - a rare but real source of confusion with arrays born from Array(n) or delete.

🌍 Real World Example

Removing an Item from an Array

Removing a known primitive value from a mutable list is the textbook indexOf() pairing: locate, guard, splice. A tag manager is a realistic host for the pattern because tags are unique strings the user removes one at a time by exact value. Notice what each piece contributes - indexOf() finds the position by strict equality, the !== -1 guard prevents splice(-1, 1) from eating the last element on a miss, and the boolean return tells the caller whether anything actually changed so the UI can re-render only when needed. In a framework-state context the same intent would be written immutably as tags.filter(t => t !== tagToRemove).

const tags = ['javascript', 'typescript', 'react', 'vue'];

function removeTag(tagToRemove) {
  const index = tags.indexOf(tagToRemove);
  if (index !== -1) {
    tags.splice(index, 1);
    return true;
  }
  return false;
}

removeTag('vue');
console.log(tags); // ['javascript', 'typescript', 'react']

Related Methods