bytes.find()

The non-raising search. Returns a BYTE offset, which is not the same as a character position once the data holds anything above ASCII.

Bytes methodPython 3.0+Live demo
Common call
data.find(b'\n')
Returns
int — byte offset, or -1 when absent
Replaces
index plus a try block, when absence is expected
Watch out
-1 is a valid index, so slicing with an unchecked result silently misbehaves
bytes.find(sub[, start[, end]])
→ int

Demo

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

find scans forward and reports where the match begins, or -1 if there is none. The multi-byte case is the one that matters: in "héllo" the letter l looks like character 2, but the accented character occupies TWO utf-8 bytes, so the byte offset is 3. Every offset here counts bytes, which is what you need for slicing the bytes object and what will not match a character position from the decoded text.

Parameters

NameTypeRequiredDescription
subbytes | intyesByte sequence to locate. An int from 0 to 255 searches for that single byte.
startintno (0)Byte offset to begin at. The result stays absolute.
endintno (len)Byte offset to stop at, exclusive.

Return value

int — Byte offset of the first occurrence of sub, or -1 if it is not present. Never raises for a missing value.

Common patterns

Split off a header
Locate the delimiter, then slice around it.
i = payload.find(b'\r\n\r\n')
header, body = payload[:i], payload[i+4:]
Optional marker
find suits the case where absence is normal.
if data.find(b'MAGIC') != -1:
    ...
Walk every occurrence
Advance past each hit until the search runs out.
i = data.find(sub)
while i != -1:
    handle(i)
    i = data.find(sub, i + 1)

Examples

1. First match
b'abcabc'.find(b'b')
Returns
1
2. Absent
b'abc'.find(b'z')
Returns
-1
3. At the start
b'abc'.find(b'a')
Returns
0
4. From an offset
b'abcabc'.find(b'b', 2)
Returns
4
5. Byte offset
'héllo'.encode().find(b'l')
Returns
3 # not 2
6. Int argument
b'a\x00b'.find(0)
Returns
1

Pitfalls

1. -1 is a valid index
The classic find trap. Slicing with an unchecked -1 means "up to the last byte" rather than "not found", so the code keeps running and produces quietly wrong data.
Silently wrong
d = b'abc'
d[:d.find(b'z')]
b'ab' # sliced to -1
Check first
i = d.find(b'z')
result = d[:i] if i != -1 else d
explicit
2. Offsets are bytes, not characters
Any non-ASCII content shifts the numbers. An offset from a bytes object cannot be used to slice the decoded string, and mixing the two is a persistent source of off-by-several bugs.
Mismatched
'héllo'.encode().find(b'l'), 'héllo'.find('l')
(3, 2)
Stay in one domain
'héllo'.find('l')   # decode first, then search
2
3. A str argument is a TypeError
Like every bytes method, it will not encode the needle for you. The error is immediate, which is at least better than a silent mismatch.
str rejected
b'abc'.find('a')
TypeError: argument should be integer or bytes-like object, not 'str'
Encode it
b'abc'.find('a'.encode())
0
4. start does not shift the answer
The returned offset is absolute, not relative to start. Treating it as an offset from where you began searching double-counts.
Read as relative
b'abcabc'.find(b'b', 2)
4, not 2
Subtract if needed
b'abcabc'.find(b'b', 2) - 2
2

When to use

Use it
  • Locating a delimiter or magic marker in a binary payload
  • Absence is expected and should not raise
  • Slicing a buffer around a known separator
Reach for something else
  • Absence is an error → index, which raises
  • You only need presence → the in operator
  • The data is text → decode first and search the string

Notes

Complexity
O(n * m) worst case; CPython uses the same optimised search as str
Return
A byte offset, or -1; never raises for a missing value
CPython impl
Objects/bytesobject.c :: bytes_find
Memory
No allocation — scans in place
Thread-safe
Yes — bytes are immutable

FAQ

Because bytes count storage and characters count text. In UTF-8 an accented letter takes two bytes, an emoji four, so byte offsets run ahead of character positions as soon as the data leaves ASCII.

'héllo'.encode().find(b'l')   # 3
'héllo'.find('l')             # 2

History

3.0
bytes.find arrived with the bytes type in the text/binary split.
3.3
An int argument accepted, searching for a single byte value.