
This is a common ValueError that happens when you are searching for a piece of text (a “substring”) inside a string, and it doesn’t exist. The message ValueError substring not found can be confusing the first time you see it.
This Error only happens when you use the .index() method.
⚡ Quick Fix: ValueError: substring not found — Python .find() and in Keyword Fix for Safe String Search Instead of .index()
You called .index() on a string that doesn’t contain the substring — .index() is strict and crashes immediately when it can’t find an exact match.
# WRONG — "goodbye" isn't in the string, .index() crashes
message = "Hello, world!"
position = message.index("goodbye") # ValueError: substring not found
# WRONG — case-sensitive mismatch: "World" is not "world"
position = message.index("World") # ValueError: substring not found
# FIX 1 — .find(): returns -1 when the substring is missing, never crashes
position = message.find("goodbye")
if position == -1:
print("Not found.")
else:
print(f"Found at index {position}.")
# FIX 2 — in keyword: the Pythonic check before committing to .index()
if "goodbye" in message:
position = message.index("goodbye")
print(f"Found at index {position}.")
else:
print("Not found.")
# FIX 3 — try/except: use when missing substring is rare and ignorable
try:
position = message.index("goodbye")
except ValueError:
position = -1
Use .find() as your default for any search where the substring might not exist.The Cause: .index() vs. .find()
Python gives you two ways to find the position of text in a string:
.index(text): This method is strict. It believes the text must exist. If it finds it, it returns the starting position (e.g.,0). If it doesn’t find it, it crashes..find(text): This method is “safe.” If it finds it, it returns the starting position. If it doesn’t find it, it returns-1(and doesn’t crash).
Problem Code:
message = "Hello, world!"
# Try to find a substring that isn't there
position = message.index("goodbye")
# CRASH! ValueError: substring not foundThe Fix: Use .find() or an in Check
Choose the fix that matches your goal.
Fix 1: Use .find() (The “Safe” Way)
This is the best fix if you just want to check if something exists.
message = "Hello, world!"
position = message.find("goodbye")
if position == -1:
print("Substring was not found.")
else:
print(f"Substring found at index {position}.")
# Output: Substring was not found. (No crash)Fix 2: Use the in Keyword (The “Pythonic” Way)
If you just want to know if the substring exists (and you don’t care where it is), use the in keyword.
message = "Hello, world!"
if "goodbye" in message:
# This code will only run if it's found
position = message.index("goodbye")
print(f"Substring found at index {position}.")
else:
print("Substring was not found.")ValueError: substring not found — .index() vs .find() and the Three Search Patterns Every Python Developer Needs
ValueError: substring not found fires exclusively from .index(). Python’s string search methods split into two camps: strict methods that crash on a miss, and safe methods that return a sentinel value. Knowing which to reach for eliminates this error permanently.
Two methods search for a substring position. One is strict, one is safe.
.index(sub) crashes with ValueError when sub is missing. Use it only when the substring must exist — parsing a known file format, extracting a required field from a validated string, or any case where a missing substring is a genuine program error that should stop execution immediately.
.find(sub) returns -1 when sub is missing. Use it for all other cases — user input, API responses, CSV data, log files, and any string where the substring might or might not be present. Check the return value against -1 before using the position.
Three patterns cover every real-world substring search.
Position needed, substring guaranteed: .index(). Wrap in try/except ValueError if you want a clean error message instead of a raw traceback.
Position needed, substring uncertain: .find() with an if position != -1: guard.
Existence only, position irrelevant: in keyword. if “error” in log_line: is faster to read, faster to type, and communicates intent immediately — you care whether it’s there, not where.
The case-sensitivity rule applies to all three. “World” and “world” are different substrings. .find(“World”) on “Hello, world!” returns -1. Add .lower() to both sides when case doesn’t matter:
if “goodbye” in message.lower():
position = message.lower().find(“goodbye”)
The re module handles substring search with patterns, wildcards, and case-insensitive flags when exact string matching isn’t enough — import re; re.search(r”go+dbye”, message, re.IGNORECASE) finds “goodbye”, “gooodbye”, and “Goodbye” in one call.





