bytes.rfind()

The search runs from the right, but the answer is an ordinary left-counted offset. The standard tool for splitting on the last separator.

Bytes methodPython 3.0+Live demo
Common call
i = path.rfind(b'/')
Returns
int — byte offset from the LEFT, or -1
Replaces
reversing the buffer and searching forward
Watch out
-1 is a valid index, so slicing with an unchecked result misbehaves
bytes.rfind(sub[, start[, end]])
→ int

Demo

Live evaluation
Try:
Inputs
sstrdata (encoded as utf-8)
substrsequence to find
Output
bytes('abcabc', 'utf-8').rfind(bytes('b', 'utf-8'))
4

In abcabc the byte b sits at 1 and 4; rfind returns 4, the last one, while find would return 1. The number is still counted from the left — only the scan direction changed. An absent sequence gives -1. The empty sequence matches at every position, so its last match is the very end, giving the length. The multi-byte case shows the offset is in bytes: the last l in héllo is at byte 4, not character 3.

Parameters

NameTypeRequiredDescription
subbytes | intyesByte sequence to locate. An int from 0 to 255 searches for that single byte.
startintno (0)Left boundary of the searched region. Still the LEFT boundary, even though the scan is from the right.
endintno (len)Right boundary, exclusive.

Return value

int — Byte offset of the last occurrence of sub, or -1 if absent. Never raises for a missing value.

Common patterns

Split off the last path component
Everything after the final slash.
leaf = path[path.rfind(b'/') + 1:]
Find a file extension
The last dot, not the first.
i = name.rfind(b'.')
ext = name[i:] if i != -1 else b''
Walk matches backwards
Shrink the end bound after each hit.
i = data.rfind(sub)
while i != -1:
    handle(i)
    i = data.rfind(sub, 0, i)

Examples

1. Last of two
b'abcabc'.rfind(b'b')
Returns
4
2. find differs
b'abcabc'.find(b'b')
Returns
1
3. Absent
b'abc'.rfind(b'z')
Returns
-1
4. Empty sub
b'abc'.rfind(b'')
Returns
3
5. Bounded
b'abcabc'.rfind(b'b', 0, 4)
Returns
1
6. Int argument
b'a\x00b\x00'.rfind(0)
Returns
3

Pitfalls

1. The result is not counted from the right
Only the scan is reversed. People expect a distance from the end or a negative index and get an ordinary left-counted offset.
Expected from the end
b'abcabc'.rfind(b'b')
4 # not 1, not -2
Convert if needed
len(d) - d.rfind(b'b') - 1
1 # distance from the end
2. -1 is a valid index
Slicing with an unchecked -1 quietly means "the last byte". The classic find-family bug, and rfind has it too.
Silently wrong
d = b'abc'
d[d.rfind(b'z') + 1:]
b'abc' # -1 + 1 = 0, whole buffer
Check first
i = d.rfind(b'z')
leaf = d[i + 1:] if i != -1 else d
explicit
3. start is still the LEFT boundary
Despite the reversed scan, start and end describe the slice s[start:end]. start does not mean "where to begin scanning from the right".
Misread
b'abcabc'.rfind(b'b', 2)
4 # searched s[2:], last hit
Bound with end
b'abcabc'.rfind(b'b', 0, 4)
1

When to use

Use it
  • Splitting on the LAST separator — paths, extensions, host:port
  • Absence is normal and should give -1 rather than raise
  • Walking matches from the end backwards
Reach for something else
  • Absence is an error → rindex
  • The FIRST occurrence → find
  • Splitting a path → os.path or pathlib handle the edge cases

Notes

Complexity
O(n * m) worst case; CPython uses an optimised reverse search
Return
A byte offset from the left, or -1
CPython impl
Objects/bytesobject.c :: bytes_rfind
Memory
No allocation — scans in place, no reversed copy
Thread-safe
Yes — bytes are immutable

FAQ

Because rfind returns a position, not a distance. Positions count from zero at the left regardless of scan direction, which is what makes the result directly usable in a slice.

d = b'a/b/c'
d[d.rfind(b'/') + 1:]   # b'c'

History

3.0
bytes.rfind arrived with the bytes type in the text/binary split.