Using Python String rindex to Find the Last Occurrence
python string rindex: Learn how Python's str.rindex() finds the last occurrence of a substring, handles errors, and compares with rfind.
python string rindex requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
When you need to locate the last occurrence of a substring within a Python string, the str.rindex() method gives you the index directly. It behaves like str.index() but searches from the end of the string instead of the beginning. This is useful when you want to extract the portion after the final delimiter, such as the file extension from a path or the last segment of a URL.
The method is called on a string and takes the substring you are looking for as its first argument. It returns the lowest index where the substring is found, but because it searches from the right, that index is actually the start of the last occurrence. If the substring is not present, it raises a ValueError.
How rindex Handles Parameters and Return Values
str.rindex(sub[, start[, end]]) accepts up to three arguments. The required sub is the substring to search for. The optional start and end define the slice of the original string to search within, using the same semantics as slicing. The search is performed only on that slice, but the returned index is still relative to the full string, not the slice.
For example:
text = "the quick brown fox jumps over the lazy dog" last_the = text.rindex("the") print(last_the) # 31
Here, rindex scans from the end and finds the "the" that starts at index 31. If you provide start and end, the search is restricted to that range. This is helpful when you want to ignore occurrences after a certain point.
text = "apple, banana, cherry, banana, date" last_banana = text.rindex("banana", 0, 20) print(last_banana) # 8
In this case, the search stops before index 20, so the second "banana" at index 21 is ignored, and the method returns 8.
Practical Examples for Common Tasks
A frequent use case is extracting the file extension from a path. Since the last dot usually separates the extension, rindex is a natural fit:
filename = "report.final.v2.pdf" dot_index = filename.rindex(".") extension = filename[dot_index + 1:] print(extension) # pdf
Similarly, you can get the last path component of a URL:
url = "https://example.com/blog/latest-news" last_slash = url.rindex("/") slug = url[last_slash + 1:] print(slug) # latest-news
These examples show that rindex is more direct than manually reversing the string or using a loop when you need the position of the final occurrence.
Handling ValueError When the Substring Is Missing
If the substring is not found, rindex raises a ValueError. This is different from rfind, which returns -1. You need to handle this exception when the presence of the substring is not guaranteed. A typical pattern is:
text = "hello world" try: pos = text.rindex("x") except ValueError: pos = -1
Alternatively, you can check first with in:
if "x" in text: pos = text.rindex("x") else: pos = -1
The choice depends on whether you prefer exception handling or a conditional check. In performance-sensitive code, the in check does an additional scan, so catching the exception might be slightly more efficient, but the difference is negligible for most applications.
Comparing rindex and rfind
str.rfind() is the companion method that does not raise an exception; it returns -1 when the substring is absent. The choice between rindex and rfind often comes down to whether a missing substring is an expected condition or an error. If the absence is a normal part of the flow, rfind avoids the try/except boilerplate. If the absence indicates a bug or an invalid input, rindex forces you to handle it explicitly.
| Method | Returns on missing substring | Raises exception? |
|---|---|---|
| rindex | - | ValueError |
| rfind | -1 | No |
For example, when parsing user input where a delimiter might be optional, rfind is more convenient:
data = "key=value" eq = data.rfind("=") if eq != -1: key = data[:eq] value = data[eq+1:]
If you used rindex here, you would need a try/except even though the missing delimiter is a valid case.
Performance and Memory Behavior
rindex performs a linear scan of the string from right to left, comparing characters until it finds a match or exhausts the string. It does not create a copy of the string or use regular expressions, so its memory overhead is constant. The time complexity is O(n) in the length of the string, similar to index and find. For most strings this is not a concern, but if you are searching in a very large text repeatedly, consider whether a different data structure, such as a suffix array or a precomputed index, would be more appropriate.
The optional start and end parameters can reduce the search space. If you know the substring cannot appear before a certain position, set start to that index to avoid scanning the earlier part of the string. This is a simple optimization that does not change the asymptotic complexity but can reduce constant factors.
Edge Cases and Compatibility Notes
rindex works with any substring, including empty strings. An empty substring is considered to be present at every position, so "hello".rindex("") returns the length of the string (5 in this case). This is consistent with the behavior of index and find.
The method is available on all built-in string types in Python 3. In Python 2, unicode objects also have rindex. There is no difference in behavior between Python versions for standard ASCII strings. For Unicode strings, the index returned is the character index, not the byte index, which is important when dealing with multi-byte characters. For example, "café".rindex("é") returns 3, not 4, because the string has four characters.
When using rindex with overlapping substrings, it returns the start of the last occurrence. For example, "ababa".rindex("aba") returns 2, because the substring "aba" appears at index 0 and 2, and the last one starts at 2. The search does not skip overlapping matches; it simply scans from the end and returns the first match it finds, which is the one with the highest start index.