String.prototype.indexOf()
Use it when you need the position — to slice around it. When you only want to know whether the text is there, includes says so without the -1 ceremony.
Demo
Only the FIRST occurrence is reported — 'hello' has two l's and the answer is 2, not a list. The second case is the one that causes bugs: a match at the very start returns 0, which is falsy, so `if (s.indexOf(x))` treats a successful match at position 0 as a failure. That is why the correct comparison is against -1 explicitly, or better, why includes exists. An empty search string returns 0 for the same reason includes('') is true — it occurs trivially at the beginning.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| searchString | string | yes | Text to find. Unlike includes, a RegExp is not rejected — it is stringified, which almost never does what you want. |
| position | number | no (0) | Where to start searching. For lastIndexOf this instead means the highest index the match may START at, searching backwards. |
Return value
number — The index of the first occurrence at or after position, or -1 if there is none. Zero is a valid result, which is what makes truthiness checks wrong.
Common patterns
const i = s.indexOf(':'); const [k, v] = [s.slice(0, i), s.slice(i + 1)];
const ext = name.slice(name.lastIndexOf('.') + 1);
let i = -1; while ((i = s.indexOf(x, i + 1)) !== -1) hits.push(i);
Examples
Pitfalls
if ('hello'.indexOf('h')) { /* never runs */ }
if ('hello'.indexOf('h') !== -1) { }
'hello'.indexOf('h') > 0
'hello'.includes('h')
'hello'.lastIndexOf('l', 2)
'hello'.lastIndexOf('l')
'abc'.indexOf(/b/)
'abc'.search(/b/)
When to use
- You need the position, usually to slice around it
- Finding the first or last delimiter in a path or key-value string
- Walking every occurrence with the position argument
- You only want a yes/no answer → includes
- Checking the beginning or end → startsWith, endsWith
- You need a pattern → search, which returns an index too
- Membership in a list → split then Array.includes
Notes
FAQ
It dates to C, where returning an out-of-band integer was the convention, and JavaScript inherited it via Java. It is the reason every membership test needs an explicit comparison — the failure value is truthy while a perfectly good result, 0, is falsy. includes exists precisely to avoid it.
s.indexOf(x) !== -1; // correct s.includes(x); // clearer