ValueError: substring not found (tested)
ValueError: substring not found comes from str.index() or str.rindex(). We measured the four usual reasons text you can see isn't found, and a trap in the most common fix: find() returning -1 that quietly turns into a wrong slice.
1. index() raises, find() doesn't
line = "Content-Type: text/html"
print(line.find("charset="))
try:
line.index("charset=")
except ValueError as e:
print(repr(e))
try:
[1, 2, 3].index(4)
except ValueError as e:
print(repr(e))
try:
"a,b".split(",").index("c")
except ValueError as e:
print(repr(e))
-1
ValueError('substring not found')
ValueError('4 is not in list')
ValueError("'c' is not in list")
Measured: str.index() raises ValueError: substring not found when the text isn't there; str.find() does the same search and returns -1 instead. The message never says what was searched for or in what, so log both. The list version reads differently (4 is not in list), which is how you tell from the message alone whether the .index() was on a string or on a list, for example a list from split().
2. Why text that's “clearly there” isn't found
for s, sub in [("Hello World", "hello"), ("price: 10", "price: "),
("name =x", "name ="), ("café", "café")]:
print(repr(s), repr(sub), s.find(sub))
print("Hello World".lower().find("hello"))
import unicodedata
print(unicodedata.normalize("NFC", "café").find(unicodedata.normalize("NFC", "café")))
'Hello World' 'hello' -1
'price: 10' 'price: ' -1
'name\xa0=x' 'name =' -1
'café' 'café' -1
0
0
Measured: all four searches failed: case (Hello vs hello), an extra space, a non-breaking space (\xa0, common in text copied from web pages and PDFs) that prints like a normal one, and the same accented letter stored two ways (precomposed é vs e plus a combining accent). Lowercasing both sides fixed the first; unicodedata.normalize("NFC", ...) on both sides fixed the last. Print with repr(), as above, to see these characters.
3. Switching to find() can make it worse
line = "Content-Type: text/html"
# the bug: find() returns -1, and -1 is a valid slice index
start = line.find("charset=") + len("charset=")
print(repr(line[start:]))
# fix 1: test the result
i = line.find("charset=")
print(line[i + 8:] if i != -1 else None)
# fix 2: partition never raises
before, sep, after = line.partition("charset=")
print(repr(sep), repr(after))
'-Type: text/html'
None
'' ''
Measured: the common “fix” of replacing index() with find() and carrying on gave '-Type: text/html': -1 + len("charset=") is 7, a perfectly valid slice start, so wrong data flows on with no error. Either check for -1 explicitly, or use str.partition(), which never raises and returns an empty separator when the text isn't found. If the substring must be there, keep index(): the exception is the correct behavior and catching it with try/except ValueError lets you report which input was bad.
4. rindex() and start positions
text = "a=1;b=2"
print(text.rfind(";"), text.rindex(";"))
try:
text.rindex("|")
except ValueError as e:
print(repr(e))
print(text.index("=", 2))
try:
text.index("a", 1)
except ValueError as e:
print(repr(e))
print("b" in text, text.count("="))
3 3
ValueError('substring not found')
5
ValueError('substring not found')
True 2
Measured: rindex() searches from the right and raises the same message. Passing a start position (text.index("a", 1)) searches only from there, so text that exists earlier in the string still raises. If you only need to know whether it's present, in and count() never raise.
Frequently asked questions
What does ValueError: substring not found mean?
str.index() or str.rindex() didn't find the text you passed. We measured that str.find() does the same search and returns -1 instead of raising.
Why is my substring not found when I can see it?
Usually case, extra or non-breaking spaces (\xa0), or a differently encoded accent. We measured all four failing; print both strings with repr() and normalize case and Unicode (NFC) before searching.
Should I use find() instead of index()?
Only if you check for -1. We measured line[line.find('charset=') + 8:] returning '-Type: text/html' with no error when the text was missing; str.partition() is a safer alternative.
What is the difference between 'substring not found' and 'is not in list'?
Same idea, different types: str.index() says 'substring not found', list.index() says "'c' is not in list". We measured both, including a list produced by split().
How do I check if a substring exists without an exception?
Use 'sub in text' or text.count(sub); neither raises.
Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5: str.index, str.rindex and str.find with missing text, case differences, extra and non-breaking spaces, NFC vs NFD accents, start positions, list.index, and the find() -1 slicing trap. Every code block was run verbatim and its output pasted from that run. PlainHeaders is our extension; everything else here is built-in Python.