How to find the last occurrence of a substring in JavaScript

How to find the last occurrence of a substring in JavaScript

When you need to find the position of a substring within a string, lastIndexOf is your go-to method. Unlike indexOf, which searches from the start, lastIndexOf begins its search from the end, scanning backwards. That’s particularly useful when you want to find the last occurrence of a character or a sequence without manually reversing strings or complicating your logic.

The method signature looks simple enough:

str.lastIndexOf(searchValue, fromIndex)

Here, searchValue is the string you are looking for, and fromIndex is optional – it tells JavaScript where to start searching backwards from. By default, it starts at the end of the string.

Consider this:

const text = "The quick brown fox jumps over the lazy dog";

console.log(text.lastIndexOf("the"));      // 31
console.log(text.lastIndexOf("The"));      // 0
console.log(text.lastIndexOf("cat"));      // -1
console.log(text.lastIndexOf("o", 20));    // 12

Notice how searching for "the" returns 31, which is the start index of the last “the” in the string, while "The" (capitalized) returns 0 because JavaScript string searches are case-sensitive. If the substring isn’t found, it returns -1, a nice, clean way to handle “not found” cases.

The fromIndex parameter is often overlooked but can be a powerful tool. For example, if you want to find the last occurrence of a character but only up to a certain point in the string, you can specify fromIndex. That is handy in parsing tasks, like extracting file extensions or searching within a substring.

Here’s an example that strips off everything after the last slash in a URL path:

const url = "https://example.com/path/to/resource.html";

const lastSlash = url.lastIndexOf("/");
const fileName = url.substring(lastSlash + 1);

console.log(fileName); // "resource.html"

Using lastIndexOf here avoids splitting the entire string or using regex, which can be overkill. It is a simple, efficient way to pinpoint that last marker.

Remember that lastIndexOf works only on strings. If you need similar functionality with arrays, there’s a separate lastIndexOf method on Array prototypes, but it behaves a bit differently, mainly because arrays aren’t continuous strings but collections of elements.

One tricky edge case is when fromIndex is set to a value larger than the string length – it just treats it as the string’s length, effectively searching the whole string backward. If you set fromIndex to a negative number, it treats it as zero, meaning the search will effectively check only the first character.

For example:

const phrase = "hello world";

console.log(phrase.lastIndexOf("o", 4));  // 4
console.log(phrase.lastIndexOf("o", -3)); // -1

In the first call, it searches backward starting at index 4 and finds the “o” at index 4 itself. In the second call, because fromIndex is negative, it only looks at index 0 and doesn’t find “o”.

It’s worth noting that lastIndexOf is a simpler method and does not support regular expressions. If you find yourself needing to match patterns instead of fixed substrings, you’ll have to look elsewhere – that’s where regex comes in.

Using regular expressions for advanced substring searches

JavaScript’s built-in lastIndexOf method is great for fixed strings, but what if your search criteria need to be more flexible? Suppose you want to find the last occurrence of a pattern, such as any digit, whitespace, or a complex sequence that can’t be expressed as a simple substring. That’s where regular expressions become indispensable.

Regular expressions let you define search patterns that can match a broad range of strings. Unfortunately, JavaScript doesn’t provide a direct lastIndexOf-style method that works with regex. The standard String.prototype.match or RegExp.prototype.exec methods find matches forward, from the start of the string. To find the last match, you have to get creative.

One common pattern is to use a global regex (with the g flag) to find all matches, then pick the last one. Here’s an example that finds the last digit in a string:

const str = "abc123def456ghi789";

const regex = /d/g;
let lastMatchIndex = -1;
let match;

while ((match = regex.exec(str)) !== null) {
  lastMatchIndex = match.index;
}

console.log(lastMatchIndex); // 14, index of '7' in '789'

In this snippet, the exec method is called repeatedly, each time returning the next match. Because the regex has the g flag, its internal lastIndex property advances automatically. By storing the index of each match, you can identify the last one after the loop finishes.

This approach works well for simple patterns, but what if you want to find the last occurrence of a more complex pattern, like a word starting with “foo” followed by digits?

const text = "foo1 bar foo23 baz foo456";

const pattern = /food+/g;
let lastMatch = null;
let result;

while ((result = pattern.exec(text)) !== null) {
  lastMatch = result;
}

if (lastMatch) {
  console.log(Last match: '${lastMatch[0]}' at index ${lastMatch.index});
  // Last match: 'foo456' at index 20
}

Because exec returns an array with the matched text and the index where it was found, you get both the content and position of the last match. This is far more powerful than lastIndexOf for substring searches.

If you want to avoid the loop, another trick is to use String.prototype.matchAll, which returns an iterator over all matches:

const matches = [...text.matchAll(/food+/g)];
const last = matches.pop();

if (last) {
  console.log(Last match: '${last[0]}' at index ${last.index});
}

This is cleaner and leverages modern JavaScript features. However, keep in mind that matchAll is relatively new and may require polyfills in older environments.

Sometimes, you want to perform a search starting backward from a certain position, similar to lastIndexOf’s fromIndex. Regex doesn’t provide built-in backward searching, but you can emulate it by slicing the string up to that position and searching forward:

const input = "foo123 foo456 foo789";
const fromIndex = 15; // search backward from here

const substring = input.slice(0, fromIndex + 1);
const pattern = /food+/g;
let lastMatch = null;
let result;

while ((result = pattern.exec(substring)) !== null) {
  lastMatch = result;
}

if (lastMatch) {
  console.log(Last match before index ${fromIndex}: '${lastMatch[0]}' at index ${lastMatch.index});
  // Last match before index 15: 'foo456' at index 7
}

By restricting the search space, you simulate a backward search even though the regex engine always scans forward.

For more advanced needs, some libraries offer regex engines that support reverse searching or provide utility functions to find last matches more efficiently. But for most practical purposes, this pattern of iterating through matches and capturing the last one will do the trick.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *