Today I was surprised to learn that the behavior for _.indexOf is not equivalent to indexOf on Strings.

> \_.indexOf("abc", "bc")
> < -1
> "abc".indexOf("bc")
> < 1
> 

What?

Before I explain, let’s note that there are two distinct indexOf functions, one for Strings and one for Arrays. Interestingly, the two have differing browser support for IE — String.prototype.indexOf has IE6+ support while Array.prototype.indexOf has IE9+ support.

_.indexOf is for arrays. Note that it is under the “Array” heading and the documentation suggests that it takes an array, not a string. Unfortunately, the intent of _.indexOf is obfuscated when stumbling upon its usage in a codebase.

Of course, this subtlety is more misleading if you were to think “Strings are just arrays of characters!” and not appreciate the differences in implementation of the String.prototype.indexOf and Array.prototype.indexOf. String’s indexOf performs substring operations while Array’s cannot.

To illustrate the point, consider the differences between "abc".indexOf("bc") and ["a","b","c"].indexOf(["b", "c"]). The former returns 1 and the latter -1.

Reading the _.indexOf’s implementation we can walk through what happens when we try to run _.indexOf("abc", "bc"). Underscore first attempts to delegate to Array.prototype.indexOf, which fails because "abc".indexOf === String.prototype.indexOf !=== Array.prototype.indexOf. Next, Underscore.js loops through the string, one character at a time, and compares it and the item.

"a" !== "ab"
"b" !== "ab"
"c" !== "ab"

Finally, we return false.

Confusingly, _.indexOf actually works for strings iff the item is a single character (e.g. _.indexOf("abc", "c") returns 2).

Lessons learned: If you use _.indexOf, please know that it is only intended for usage when Array.prototype.indexOf on the parameters is appropriate. If you want Array.prototype.indexOf for pre-IE9 browsers and you also have Underscore.js, use _.indexOf. If you need to do substring operation, use String.prototype.indexOf.