str.rfind()

Locate a substring's LAST occurrence: highest index, or -1 when absent. The right-scanning mirror of str.find.

String methodPython 2.0+Live demo
Common call
filename.rfind(".")
Returns
int index, or -1 when absent
Replaces
str.rindex raises ValueError instead of returning -1
Watch out
-1 is truthy — `if s.rfind(x):` is a classic bug
str.rfind(subsubThe substring to locate.type: str · required, startstartSlice start, supports negative indexing. The search is bounded to s[start:end].type: int · default: 0=0, endendSlice end (exclusive), supports negative indexing.type: int · default: len(s)=len(s))
int

Demo

Live evaluation
Try:
Inputs
stringstrthe source
substrto locate
startintsearch from
Output
'hello world'.rfind('o')
7

rfind scans from the RIGHT: it returns the HIGHEST index where sub appears in the substring s[start:end], or -1 when there is no occurrence. The start / end bounds are applied first, then the search runs backward. An empty sub is found at every position — rfind returns end (or len when end is default).

Parameters

NameTypeRequiredDescription
substryesThe substring to locate.
startintno (0)Slice start, supports negative indexing. The search is bounded to s[start:end].
endintno (len(s))Slice end (exclusive), supports negative indexing.

Return value

intThe highest index where sub is found, or -1 when it does not occur. Never raises for a missing substring — the non-raising sibling of str.rindex.

Common patterns

File extension via last dot
rfind is the classical way to split a filename at its final dot.
i = name.rfind(".")
ext = name[i+1:] if i != -1 else ""
Last URL segment
Trim everything up to the last slash.
i = url.rfind("/")
tail = url[i+1:] if i != -1 else url
Bounded rfind
Use start / end to search a slice without materializing it.
i = s.rfind(sub, 0, cutoff)   # search only in s[:cutoff]

Examples

1. Last occurrence
"hello world".rfind("o")
Returns
7
2. Not found
"hello".rfind("z")
Returns
-1
3. Multiple hits
"abcabc".rfind("bc")
Returns
4
4. File extension
"archive.tar.gz".rfind(".")
Returns
11
5. Bounded search
"hello world".rfind("o", 0, 5)
Returns
4
6. Empty sub is len
"hello".rfind("")
Returns
5

Pitfalls

1. The -1 sentinel is TRUTHY
A missing substring returns -1 — which is truthy in Python. `if s.rfind(x):` treats "not found" as a positive signal. Same bug as find.
Wrong branch
if line.rfind("ERROR"):
    log_error()  # runs on -1 too
runs even when ERROR is missing
Compare explicitly
if line.rfind("ERROR") != -1:
    log_error()
runs only on hit
2. rfind vs rindex — one returns -1, one raises
rindex is the raising sibling: it raises ValueError instead of returning -1 for a missing substring. Same pair as find/index.
Blows up
"hello".rindex("z")
ValueError: substring not found
Silent -1
"hello".rfind("z")
-1
3. start / end are applied FIRST — then search runs backward
You are not asking "search backward from end", you are asking "search the slice s[start:end] and return the highest index in the original string". If you slice out the target, rfind returns -1.
Assumed direction
"hello world".rfind("o", 8, 11)
-1 # "o" not in "orl"
Full slice
"hello world".rfind("o", 0, 11)
7
4. Empty substring returns end (or len)
Every position matches the empty string, and rfind returns the highest one — which is the slice's end. Handy for "no-op" edge cases; surprising if you were probing for real content.
Assumed -1
"hello".rfind("")
5 # not -1
Guard against empty
if sub and s.rfind(sub) != -1:
    ...
safer intent

When to use

Use it
  • Splitting a filename at the last dot
  • Extracting the tail after the last delimiter
  • Finding the last occurrence of any substring in a search-and-replace tool
  • When "not found" should be handled silently, not with an exception
Reach for something else
  • You want an exception on missing → str.rindex
  • You need the first occurrence → str.find
  • You need to know how many matches → str.count
  • You need every match position → a loop with find or re.finditer

Notes

Complexity
O(n * m) worst case for pattern length m in string length n; typically O(n)
Return
int in [start, end] on hit; -1 on miss; end (or len) for empty sub
CPython impl
Objects/unicodeobject.c :: unicode_rfind
Memory
No allocation
Thread-safe
Yes — strings are immutable

FAQ

rfind returns -1 on a missing substring; rindex raises ValueError. Same pair as find/index. Pick rfind when absence is expected and cheap; pick rindex when absence is a hard error you want to trip.

History

2.0
rfind() has been part of str since Python 2.0.
2.5
start / end arguments accept negative indexes.