To find the last occurrence of a substring in Python, call str.rfind(sub). It scans from the right and returns the index where the last match starts, or -1 when the substring isn’t there:
"a-b-c".rfind("-") # 3
"a-b-c".rfind("x") # -1
All the indexes quoted on this page come straight from Command Prompt runs on Python 3.12.5, using an order log, a receipt and a few file paths as sample text.
In this tutorial, we’ll cover rfind(), rindex() and its ValueError, the start and end arguments, splitting and replacing at the last match, case-insensitive searches, regex patterns and a few edge cases that trip people up.
Find the last occurrence of a substring with rfind()
Here’s a delivery log with the word shipped three times. find() gives the first position, rfind() gives the last, and slicing from that index pulls out the newest entry:
log = "Order 1001 shipped to Austin, TX; order 1002 shipped to Denver, CO; order 1003 shipped to Boston, MA"
print("first 'shipped':", log.find("shipped"))
print("last 'shipped': ", log.rfind("shipped"))
print("text after it: ", log[log.rfind("shipped"):])
print("missing word: ", log.rfind("delivered"))
Output:
first 'shipped': 11
last 'shipped': 79
text after it: shipped to Boston, MA
missing word: -1

The index always points at the first character of the match, even though the search runs from the right. If you only need a yes or no answer, checking whether a Python string contains a substring with in is simpler.
rindex() vs rfind(): which one should you use?
rindex() returns exactly the same index as rfind() when the substring exists. The difference is the miss: rindex() raises ValueError, while rfind() quietly returns -1:
path = "C:/pyguides/reports/2026/q3/sales.csv"
print(path.rindex("/")) # same result as rfind when found
try:
path.rindex("\\") # no backslash in this path
except ValueError as err:
print("ValueError:", err)
pos = path.rfind("\\")
print("rfind returns", pos, "so check for -1 yourself")
Output:
27
ValueError: substring not found
rfind returns -1 so check for -1 yourself

I reach for rindex() when a missing substring means broken data. A silent -1 is dangerous here, because s[-1:] is a valid slice and gives you the last character instead of an error.
Search part of a string with the rfind() start and end arguments
Both methods take optional start and end arguments, and they work like a slice: end is exclusive. Passing the last index as end is the easy way to get the second-to-last match:
s = "tax=1.50; tip=2.00; tax=0.75; tip=1.25; tax=3.10"
last = s.rfind("tax")
second_last = s.rfind("tax", 0, last) # search only before the last one
print("last tax at", last, "second-to-last at", second_last)
print(s.rfind("tip", 0, 25)) # end is exclusive: only s[0:25] is searched
print(s[0:25].rfind("tip")) # same answer as slicing first
print(s.rfind("tax", 41)) # start=41: no "tax" begins at 41 or later
Output:
last tax at 40 second-to-last at 20
10
10
-1
Notice that s.rfind("tip", 0, 25) and s[0:25].rfind("tip") agree. The returned index is still counted from the start of the full string, which is why the arguments beat slicing when you need real positions.
Find the nth occurrence from the end
Repeat that trick in a loop to walk backwards. This helper returns the n-th match from the right, or -1 when there aren’t enough:
def rfind_nth(text, sub, n):
"""Index of the n-th occurrence counted from the end (n=1 is the last)."""
end = len(text)
for _ in range(n):
end = text.rfind(sub, 0, end)
if end == -1:
return -1
return end
s = "tax=1.50; tip=2.00; tax=0.75; tip=1.25; tax=3.10"
for n in range(1, 5):
print(n, rfind_nth(s, "tax", n))
Output:
1 40
2 20
3 0
4 -1
When you need every position at once rather than one from the end, my guide on finding all occurrences of a substring in Python collects them in a list.
Split a string at the last occurrence using rsplit() and rpartition()
Often you just want the text on either side of the last separator. rsplit(sep, 1) splits once from the right, and rpartition(sep) always returns a three-part tuple:
filename = "report.final.2026.csv"
print(filename.rsplit(".", 1)) # split once, from the right
print(filename.rpartition(".")) # (before, separator, after)
url = "https://example.com/shop/shoes/running"
head, sep, tail = url.rpartition("/")
print("last segment:", tail)
print("no-dot".rpartition(".")) # not found: everything lands in the last slot
Output:
['report.final.2026', 'csv']
('report.final.2026', '.', 'csv')
last segment: running
('', '', 'no-dot')

That’s my go-to for file extensions and URL endings. For text that sits between two markers instead, see extracting a substring between two characters in Python.
Replace only the last occurrence of a substring
str.replace() works from the left, and its count argument can’t target the last match. Slice around the rfind() index instead, or join an rsplit() with the new text:
def replace_last(text, old, new):
i = text.rfind(old)
if i == -1:
return text
return text[:i] + new + text[i + len(old):]
menu = "coffee, tea, coffee, juice, coffee"
print(replace_last(menu, "coffee", "cocoa"))
print("cocoa".join(menu.rsplit("coffee", 1))) # one-liner, same result
print(replace_last(menu, "soda", "water")) # not found: unchanged
Output:
coffee, tea, coffee, juice, cocoa
coffee, tea, coffee, juice, cocoa
coffee, tea, coffee, juice, coffee
Both versions return the original string unchanged when the substring isn’t found. To drop a substring entirely, the options in removing a substring from a string in Python apply.
How do I find the last occurrence ignoring case?
rfind() is case-sensitive, so Dallas, DALLAS and dallas are three different words to it. Lowercase the string and the search term, or use re.IGNORECASE:
import re
note = "Ship to Dallas. Billing in DALLAS. Return to dallas office."
print(note.rfind("Dallas")) # case-sensitive: only "Dallas" matches
print(note.lower().rfind("dallas")) # lower() keeps the same length for this text
matches = list(re.finditer("dallas", note, re.IGNORECASE))
last = matches[-1]
print(last.start(), repr(last.group()))
Output:
8
45
45 'dallas'
The lower() trick works because lowercasing this text doesn’t change its length, so the index maps back to the original. For text with characters like German ß, casefold() can change the length, and the regex route is safer.
More ways to compare text without caring about capitals are in case-insensitive string comparisons in Python.
Find the last match of a regex pattern in a string
When the thing you’re looking for is a pattern, like a dollar amount, rfind() can’t help. Take the last item from re.finditer(), or use a greedy .* so re.search() lands on the final match:
import re
receipt = "Latte $4.75, bagel $3.20, refund -$1.50, muffin $2.95"
prices = list(re.finditer(r"\$(\d+\.\d{2})", receipt))
last = prices[-1]
print("last price:", last.group(1), "at index", last.start())
m = re.search(r".*\$(\d+\.\d{2})", receipt) # greedy .* jumps to the last match
print("greedy search:", m.group(1))
Output:
last price: 2.95 at index 48
greedy search: 2.95

finditer() is easier to read and also gives you start(). A digits-only version of this idea is in finding the last number in a string in Python.
Edge cases of rfind() with overlapping and empty substrings
A few results surprise people the first time they see them:
s = "banana"
print(s.rfind("ana")) # overlapping matches count: 3, not 1
print(s.rfind("")) # empty substring: returns len(s)
print(s.rfind("a", 10)) # start past the end: -1
print("".rfind("a")) # empty string: -1
Output:
3
6
-1
-1
Overlapping matches count, so "banana".rfind("ana") is 3. An empty substring returns len(s), so guard against empty user input.
Run the last-occurrence examples yourself
If you’ve never run a Python file from Command Prompt, this takes about two minutes:
- Install Python from python.org and tick Add python.exe to PATH in the installer.
- Open Notepad or VS Code, paste the rfind() example and save it as
last_occurrence.pyin a folder such asC:\pyguides. - Press Win + R, type
cmdand press Enter to open Command Prompt. - Type
cd C:\pyguides, press Enter, then runpython last_occurrence.py. - Change the search word, save th