How to check if a string contains a substring in Python
Python has no .contains() method — the answer is the in operator: if "code" in text:. It returns True or False and is the fastest thing to read and to type.
in answers whether a substring is there, never where. When you need the position, reach for .find() or .index(); when case shouldn't matter, fold both sides first; when you're matching a shape rather than fixed text, use re.search(). Every example below runs on this page — hit Run, edit, run again.
1The in operatorRecommended
substring in string is a plain boolean test — no imports, no return code to interpret. It reads like English, so use it directly in an if rather than comparing something to -1.
Output
Output: found it, True, False, False. That last line is the gotcha — in is case-sensitive, so "Code" misses the lowercase code in the text. Section 3 fixes that. Use not in for the negative check.
2find() and index() — when you need the position
Both return the index of the first occurrence. They differ only in how they report a miss: .find() returns -1, while .index() raises ValueError. Pick .find() when "not there" is a normal outcome and .index() when it means your data is wrong and you want the crash.
Output
Output: 23, -1, 23, then index raised ValueError: substring not found. Never write if text.find("x"): — a match at position 0 is falsy, so that silently misses hits at the start. Compare against -1, or just use in. Both methods also take start and end offsets, and .rfind() / .rindex() search from the right.
3Case-insensitive matching
There is no ignorecase flag on in — you normalise both sides yourself. .lower() is enough for ASCII, but .casefold() is the more correct choice: it is an aggressive, matching-oriented fold that handles cases .lower() leaves alone, such as German ß → ss.
Output
Output: False, True, False, True. The third line is .lower() failing for real: "Große".lower() is still große, so grosse isn't in it — .casefold() turns it into grosse and the match succeeds. Fold once outside the loop if you're testing many needles against the same text.
4Patterns with re.search(), edges with startswith()
in matches literal text — "c.de" in text is False even though the regex c.de matches code. When you want a shape (a date, a number, an id), use re.search(), which returns a match object or None. When you only care about the start or the end, the dedicated .startswith() / .endswith() methods say so more precisely — and both accept a tuple of candidates.
Output
Output: True, 91%, True, True. Use re.search(), not re.match() — match() only anchors at the start. And if your needle comes from user input, wrap it in re.escape() or characters like . and * will be read as pattern syntax.
5Which should you use?
| Method | Returns | If missing | Best for |
|---|---|---|---|
| "x" in text | True / False | False | Presence — the default |
| text.find("x") | Index | -1 | Position, miss is expected |
| text.index("x") | Index | ValueError | Position, miss is a bug |
| re.search(p, text) | Match or None | None | Patterns, not fixed text |
| text.startswith("x") | True / False | False | Prefix / suffix only |
6Common variation: check many substrings at once
Don't chain or across a dozen checks. Put the needles in a list and feed a generator to any() — it short-circuits on the first hit — or to all() when every term must appear.
Output
Output: True, False, ['timeout'], timeout. The comprehension gives you every match and next(..., None) gives you the first one without an IndexError on an empty result.
Frequently asked questions
Does Python have a string.contains() method?
No. Calling text.contains("x") raises AttributeError: 'str' object has no attribute 'contains'. The in operator is Python’s equivalent: "x" in text. The confusion usually comes from pandas, where a Series does have .str.contains().
What is the difference between find() and index()?
Both return the index of the first occurrence of the substring. When it is missing, find() returns -1 and index() raises ValueError: substring not found. Use find() when a miss is a normal outcome, index() when a miss means the data is wrong.
How do I check if a string contains a substring, ignoring case?
Fold both sides: needle.lower() in text.lower(). Prefer .casefold() for non-English text — it folds forms .lower() misses, so "GROSSE".casefold() in "Große Straße".casefold() is True while the .lower() version is False.
Why does if text.find("x") not work?
find() returns 0 when the substring is at the very start, and 0 is falsy — so the if skips a real match. Write if text.find("x") != -1: or, better, if "x" in text:.