str.index()

Identical to find, except for the failure mode: find returns -1, index raises. Pick the one whose failure you actually want to handle.

String methodPython 1.0+Live demo
Common call
s.index(sub)
Returns
int — the first position, counting from 0
Replaces
find() plus a manual "== -1" check you might forget
Watch out
raises ValueError on absence — wrap it or use find instead
str.index(sub[, start[, end]])
int

Demo

Live evaluation
Try:
Inputs
stringstrstring to search in
substrsubstring to find
startintstart position (blank = 0)
Output
'hello'.index('l')
2

index scans left to right and returns the first position where sub starts. With a start argument the search skips ahead, but the number you get back is still counted from the beginning of the string. The one real difference from find is the failure case: a missing substring raises ValueError rather than returning -1. The empty string is a curiosity — it is considered present everywhere, so it returns start immediately.

Parameters

NameTypeRequiredDescription
substryesSubstring to look for. The empty string is found immediately at position start.
startintno (0)Where to begin searching. The returned index is still absolute, not relative to start.
endintno (len)Where to stop. The slice searched is s[start:end].

Return value

intLowest index where sub is found. Raises ValueError if it is absent — it never returns -1.

Common patterns

Split on the first separator
When the separator is guaranteed present, index reads more directly than find.
i = line.index(":")
key, value = line[:i], line[i + 1:]
Let absence be an error
If a missing marker means malformed input, the exception is the correct behaviour.
try:
    start = doc.index("<body>")
except ValueError:
    raise ParseError("no body tag")
Walk every occurrence
Advance past each hit; the loop ends when the exception fires.
i = 0
while True:
    try:
        i = s.index(sub, i) + 1
    except ValueError:
        break

Examples

1. First match
'hello'.index('l')
Returns
2
2. From a position
'hello'.index('l', 3)
Returns
3
3. First of many
'abcabc'.index('b')
Returns
1
4. Missing raises
'hello'.index('z')
Returns
ValueError: substring not found
5. Empty is at 0
'hello'.index('')
Returns
0
6. find returns -1
'hello'.find('z')
Returns
-1 # the alternative

Pitfalls

1. It raises where find returns -1
Swapping find for index without adding a try block turns a quiet -1 into an uncaught exception. The two have identical signatures, which makes the swap look safe when it is not.
Uncaught
i = 'hello'.index('z')
ValueError: substring not found
Use find for optional
i = 'hello'.find('z')
if i == -1:
    ...
-1, handled
2. The -1 from find is a valid index
The reverse trap. Passing find's -1 straight into a slice silently means "last character" instead of "not found", which is why index exists at all.
Silently wrong
s = 'hello'
s[:s.find('z')]
'hell' # sliced to -1, not empty
Check or use index
i = s.find('z')
result = s[:i] if i != -1 else s
explicit
3. start does not shift the result
The returned index is absolute. Treating it as an offset from start double-counts, which shows up as an off-by-start bug in slicing.
Assumed relative
'hello'.index('l', 3)
3 # absolute, not 0
Subtract if you need relative
'hello'.index('l', 3) - 3
0

When to use

Use it
  • The substring is required, and absence is a genuine error
  • You want the failure to stop the code rather than flow onward
  • Parsing where a missing marker means malformed input
Reach for something else
  • Absence is normal and expected → find, which returns -1
  • You only need a yes/no answer → the in operator
  • Searching from the right → rindex or rfind

Notes

Complexity
O(n * m) worst case; CPython uses a mix of Crochemore-Perrin and Bloom filters
Return
A non-negative int; never -1, because absence raises instead
CPython impl
Objects/stringlib/find.h :: stringlib_index
Memory
No allocation — scans in place
Thread-safe
Yes — strings are immutable

FAQ

When absence is a bug rather than a case to handle. index turns a missing substring into an immediate, loud ValueError; find hands back -1 and trusts you to check. If you would write "if i == -1: raise" anyway, index already does it.

i = line.index(':')   # a line without a colon is malformed

History

1.0
Present since the earliest string methods, alongside find.
2.5
start and end accepted as None, making it easier to pass through optional arguments.