bytes.index()

Identical to find except for the failure mode. Note the error message says SUBSECTION, not substring — a small tell that you are in the bytes world.

Bytes methodPython 3.0+Live demo
Common call
data.index(b'\n')
Returns
int — byte offset; absence raises
Replaces
find plus a manual -1 check you might forget
Watch out
the message is "subsection not found", unlike str's "substring not found"
bytes.index(sub[, start[, end]])
→ int

Demo

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

Same scan as find, different ending. A match gives its byte offset; no match raises ValueError rather than handing back -1. The multi-byte case shows the offset is measured in BYTES — l sits at 3 in the encoded form of "héllo" even though it is the third character. Choose this over find when a missing marker means the input is malformed.

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. Raises ValueError with the message "subsection not found" when absent.

Common patterns

Required delimiter
When the separator must be there, the exception is the bounds check.
i = payload.index(b'\r\n\r\n')
header, body = payload[:i], payload[i+4:]
Convert absence into a domain error
Catch and re-raise with something the caller can act on.
try:
    i = data.index(b'MAGIC')
except ValueError:
    raise ParseError('missing magic bytes')
Walk every occurrence
The exception ends the loop naturally.
i = 0
while True:
    try:
        i = data.index(sub, i) + 1
    except ValueError:
        break

Examples

1. First match
b'abcabc'.index(b'b')
Returns
1
2. At the start
b'abc'.index(b'a')
Returns
0
3. From an offset
b'abcabc'.index(b'b', 2)
Returns
4
4. Absent raises
b'abc'.index(b'z')
Returns
ValueError: subsection not found
5. Byte offset
'héllo'.encode().index(b'l')
Returns
3
6. find returns -1
b'abc'.find(b'z')
Returns
-1 # the alternative

Pitfalls

1. It raises where find returns -1
The signatures are identical, so swapping one for the other looks safe. It is not — an absent value that produced a quiet -1 now stops the program.
Uncaught
b'abc'.index(b'z')
ValueError: subsection not found
Use find for optional
i = b'abc'.find(b'z')
if i == -1:
    ...
-1, handled
2. The message differs from str.index
bytes says "subsection not found" while str says "substring not found". Code or tests matching on the message text break when the type changes underneath them.
Message mismatch
except ValueError as e:
    assert 'substring' in str(e)
fails for bytes
Do not match on text
except ValueError:
    handle()
type is enough
3. A str argument is a TypeError, not a ValueError
Two different failures are easy to conflate. Passing a str is a TYPE error and will not be caught by a handler written for the not-found case.
Wrong handler
try:
    b'abc'.index('z')
except ValueError:
    ...
TypeError escapes
Encode the needle
b'abc'.index('z'.encode())
ValueError, as expected

When to use

Use it
  • A delimiter that must be present in a well-formed payload
  • Parsing where a missing marker means malformed input
  • You would write "if i == -1: raise" anyway
Reach for something else
  • Absence is normal → find, which returns -1
  • You only need presence → the in operator
  • Searching from the right → rindex

Notes

Complexity
O(n * m) worst case; the same search machinery as find
Return
A non-negative byte offset; absence raises
CPython impl
Objects/bytesobject.c :: bytes_index
Memory
No allocation — scans in place
Thread-safe
Yes — bytes are immutable

FAQ

Because a bytes object holds no strings — "substring" would imply text. It is a small wording change, but enough to break tests that assert on the message when data switches from str to bytes.

b'abc'.index(b'z')
# ValueError: subsection not found

History

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