Skip to content
JavaScript js strings 4 min read

Searching Strings

Searching inside strings is one of the most common tasks in everyday JavaScript: validating user input, filtering lists, parsing URLs, or highlighting matches. The language gives you a small but expressive toolkit, from simple boolean checks like includes to position-aware lookups like indexOf and full pattern matching with regular expressions. Knowing which method to reach for keeps your code readable and avoids reinventing logic that the standard library already handles. This page walks through each option and when to use it.

Checking for presence with includes

String.prototype.includes answers a simple yes/no question: does this substring appear anywhere in the string? It returns a boolean, which makes it perfect for if conditions. It is case-sensitive and was introduced in ES2015.

const headline = "The quick brown fox";

console.log(headline.includes("quick"));   // true
console.log(headline.includes("Quick"));   // false (case-sensitive)
console.log(headline.includes("fox", 10)); // true (start searching at index 10)

Output:

true
false
true

The optional second argument is the index to start searching from. To make the check case-insensitive, normalize both sides first with toLowerCase().

Tip: Prefer includes over indexOf(x) !== -1 when you only care about presence. It reads as plain English and removes the easy-to-forget -1 comparison.

Finding positions with indexOf and lastIndexOf

When you need the location of a match (not just whether it exists), use indexOf. It returns the zero-based index of the first occurrence, or -1 if the substring is not found. lastIndexOf does the same but searches from the end, returning the last occurrence.

const path = "/users/profile/avatar.png";

console.log(path.indexOf("/"));        // 0  (first slash)
console.log(path.lastIndexOf("/"));    // 14 (last slash)
console.log(path.indexOf("xyz"));      // -1 (not found)

// Extract the file name after the last slash
const fileName = path.slice(path.lastIndexOf("/") + 1);
console.log(fileName);                  // "avatar.png"

Output:

0
14
-1
avatar.png

Both methods accept an optional starting position. Because they return -1 for “not found”, always compare explicitly rather than relying on truthiness — index 0 is a valid match but is falsy.

Matching the start and end

To test whether a string begins or ends with a given substring, use startsWith and endsWith. They are clearer and faster to read than slicing or regex for these common checks.

const url = "https://devcraftly.com/docs";

console.log(url.startsWith("https://")); // true
console.log(url.endsWith(".com"));       // false
console.log(url.endsWith("/docs"));      // true

// endsWith can pretend the string is shorter
console.log(url.endsWith(".com", 22));   // true (treats length as 22)

Output:

true
false
true
true

startsWith accepts an optional position to begin from, while endsWith accepts an optional “end position” that treats the string as if it were that length — handy for checking a prefix of a longer string.

Method comparison

MethodReturnsCase-sensitiveUse when
includesbooleanyesYou only need presence
indexOfnumber / -1yesYou need the first position
lastIndexOfnumber / -1yesYou need the last position
startsWithbooleanyesChecking a prefix
endsWithbooleanyesChecking a suffix
searchnumber / -1regex-drivenPosition of a pattern match
matcharray / nullregex-drivenExtracting matched text or groups

Pattern matching with search and match

When a fixed substring is not enough — say you want “any digit” or “a word at a boundary” — reach for regular expressions. search is the regex-aware cousin of indexOf: it returns the index of the first match or -1.

const log = "Error 404: page not found";

console.log(log.search(/\d+/));        // 6  (index of "404")
console.log(log.search(/warning/i));   // -1 (no match)

Output:

6
-1

match goes further and returns the matched text. Without the global flag it returns an array containing the full match plus any capture groups; with the /g flag it returns every match as a flat array (or null if nothing matches).

const text = "Contact us at [email protected] or [email protected]";
const emailPattern = /[\w.]+@[\w.]+/g;

const emails = text.match(emailPattern);
console.log(emails);
console.log(emails?.length ?? 0);

Output:

[ '[email protected]', '[email protected]' ]
2

Warning: match returns null (not an empty array) when there are no matches. Guard with optional chaining (result?.length) or the nullish coalescing operator before iterating, or you will hit a TypeError.

For richer extraction — capture groups with index information — matchAll returns an iterator of all matches, which you can spread into an array. Regex details are covered on their own page; this is just the tease.

Best practices

  • Use includes, startsWith, and endsWith for plain substring checks — they are self-documenting and avoid -1 comparisons.
  • Reserve indexOf/lastIndexOf for when you actually need the numeric position, and always compare against -1 explicitly.
  • Normalize case with toLowerCase() on both operands when you need case-insensitive matching.
  • Reach for search/match only when a fixed substring cannot express the pattern; a literal string check is faster and clearer.
  • Always guard match results against null before accessing .length or iterating.
  • Prefer matchAll over a global match when you need capture groups or match indices.
Last updated June 1, 2026
Was this helpful?