Searching text is one of the most common things programs do. Does this email contain an @? Where does the file extension start? Does the message mention a keyword? JavaScript has a set of search methods, and the trick is knowing which one answers which question.
| Question | Method | Returns |
|---|---|---|
| Is it in there? | includes | true / false |
| Does it begin / end with it? | startsWith / endsWith | true / false |
| Where is it? | indexOf / lastIndexOf | position, or -1 |
| Where is the pattern? | search | position, or -1 |
| What matched? | match | array, or null |
| Every match, with details | matchAll | an iterator to loop over |
Every one of these is case sensitive: "Hello" does not contain "hello".
The simplest search: is this text in there? It returns true or false:
let text = "Hello world, welcome to the universe.";
console.log(text.includes("world"));
console.log(text.includes("world", 12)); // only search from position 12 on
The optional second value is where to start searching. "world" is at position 6, so starting at 12 misses it.
let file = "report-2026.pdf";
console.log(file.startsWith("report"));
console.log(file.endsWith(".pdf"));
console.log(file.endsWith(".doc"));
Both take an optional second value, but they mean different things:
let text = "Hello world, welcome to the universe.";
console.log(text.startsWith("world", 5)); // pretend the string starts at 5
console.log(text.startsWith("world", 6)); // ...or at 6
console.log(text.endsWith("world", 11)); // only look at the first 11 characters
For startsWith it is a start position; position 5 is the space before "world". For endsWith it is a length: treat the string as if it were only 11 characters long ("Hello world"), which does end with "world".
indexOf() tells you where: the position of the first match, or -1 if there is none. lastIndexOf() gives the position of the last match:
let text = "Please locate where 'locate' occurs!";
console.log(text.indexOf("locate"));
console.log(text.lastIndexOf("locate"));
console.log(text.indexOf("John")); // not found
Positions count from zero, like everything else in strings. Both methods take an optional starting position. indexOf searches forward from it; lastIndexOf searches backward from it, toward the beginning:
console.log(text.indexOf("locate", 15)); // forward from 15: finds the second
console.log(text.lastIndexOf("locate", 15)); // backward from 15: finds the first
0, which is falsy. So if (text.indexOf("Please")) is false even though "Please" was found. Always compare: if (text.indexOf("Please") !== -1), or just use includes() when you only need yes or no.
Finding a position is usually a step toward cutting the string:
let email = "alex@example.com";
let at = email.indexOf("@");
let user = email.slice(0, at);
let domain = email.slice(at + 1);
console.log(user);
console.log(domain);
// File extension: search from the end, since names can contain dots
let file = "archive.backup.zip";
console.log(file.slice(file.lastIndexOf(".") + 1));
All these methods are case sensitive. Lowercase both sides to ignore capitals:
let msg = "Meeting moved to FRIDAY";
console.log(msg.includes("friday"));
console.log(msg.toLowerCase().includes("friday"));
The last three methods can take a regular expression: a search pattern written between slashes, like /ain/. Regular expressions get their own section later; here is just enough to read these examples:
/ain/ matches the letters "ain"/\d/ matches any digit; /\d+/ matches a run of one or more digitsg after the last slash means global: find all matches, not just the firsti means ignore casesearch() returns the position of the first match, or -1:
let text = "Please locate where 'locate' occurs!";
console.log(text.search("locate"));
console.log(text.search(/locate/));
let order = "Order 42 shipped";
console.log(order.search(/\d/)); // position of the first digit
That looks like indexOf(), and for plain text it gives the same answer. The differences: search() cannot take a start position, and indexOf() cannot take a pattern. Only search() could answer "where is the first digit?"
match() returns what was found, as an array:
let text = "The rain in SPAIN stays mainly in the plain";
console.log(text.match(/ain/g)); // every match
console.log(text.match(/ain/gi)); // every match, ignoring case
console.log(text.match(/xyz/g)); // no match at all
Without g, match() returns only the first match (plus extra details, like its index). And when nothing matches, it returns null, not an empty array.
text.match(/a/g).length is a handy way to count occurrences, but if there are none, match returns null, and null.length is a TypeError. Guard it: (text.match(/a/g) || []).length gives 0 instead of crashing.
matchAll() (ES2020) finds every match along with its details, like where each one is. It returns an iterator, a sequence you step through with a for...of loop:
let text = "I love cats. Cats are very easy to love. Cats are very popular.";
for (const m of text.matchAll(/Cats/gi)) {
console.log(m[0] + " at position " + m.index);
}
If you pass a regular expression, it must have the g flag, or matchAll throws a TypeError. Add i as well to ignore case, as above.
You only need to know whether a username contains a space. Which method is the best fit?
indexOf(" ")includes(" ")match(/ /g)lastIndexOf(" ")What does this log, and why?
let s = "Hello";
if (s.indexOf("H")) {
console.log("found");
} else {
console.log("not found");
}
not found, even though "H" is there. It is at position 0, and 0 is falsy. Use s.indexOf("H") !== -1 or s.includes("H").What does "banana".match(/a/g) return?
31["a", "a", "a"]truematch with g returns an array of every match. Add .length to get the count, 3.url starts with "https://"indexOf and slice"photo.final.v2.jpg"match(/a/g). Then try a sentence with no "a" at all, and fix the crash with || []