str.find()
Locate a substring: its first index, or -1 when absent — the non-raising sibling of str.index.
Common call
"hello".find("l")
Returns
int index, or -1 when absent
Replaces
str.index raises ValueError instead of returning -1
Watch out
-1 is truthy — "if s.find(x):" is a classic bug
str.find(subsub — The substring to locate.type: str · required, startstart — Slice start, supports negative indexing.type: int · default: 0=0, endend — Slice 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'.find('o')
4
find scans left to right and reports the FIRST match. A missing substring returns -1 — no exception. The optional start restricts where the search begins.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| sub | str | yes | The substring to locate. |
| start | int | no (0) | Slice start, supports negative indexing. |
| end | int | no (len(s)) | Slice end (exclusive), supports negative indexing. |
Return value
int — The lowest index where sub is found, or -1 when it does not occur. Never raises for a missing substring.
Common patterns
Check-then-slice
Find a marker and slice relative to it.
i = s.find(":") if i != -1: value = s[i + 1:]
Existence check — prefer "in"
When you only need yes/no, the in operator says it better.
if "@" in email: # not: email.find("@") != -1 ...
Search from the right
rfind reports the LAST occurrence.
ext = path[path.rfind(".") + 1:]
Examples
1. First occurrence
"hello".find("l")
Returns
22. Missing substring
"hello".find("z")
Returns
-13. Search from an index
"hello world".find("o", 5)
Returns
74. Empty sub matches at 0
"abc".find("")
Returns
0Pitfalls
1. -1 is truthy
Testing find() directly as a boolean inverts the logic: 0 (found at start) is falsy, -1 (absent) is truthy.
Backwards
if "hello".find("h"): print("found")
nothing — index 0 is falsy
Fix
if "hello".find("h") != -1: print("found")
found
2. find vs index
Same search, different failure mode — pick by whether absence is an error.
Silent -1 travels
pos = s.find(marker) value = s[pos:] # pos may be -1!
slice from -1 = last char — silently wrong
Fail fast
pos = s.index(marker) # raises if absent value = s[pos:]
ValueError at the real problem site
When to use
Use it
- You need the position, and absence is a normal case
- Slicing around a found marker
Reach for something else
- Existence only → "sub" in s
- Absence is a bug → str.index (raises)
- Last occurrence → str.rfind
- Pattern matching → re.search
Notes
Complexity
O(n·m) worst case; tuned two-way search in practice
Return
int (or -1)
CPython impl
Objects/unicodeobject.c :: unicode_find
Memory
No allocation
Thread-safe
Yes — str is immutable
FAQ
Identical search; find returns -1 for a missing substring while index raises ValueError. Use index when absence means a bug.
History
2.0
Method available on the unified string type.