Back to Blog
Python

Using Python String find() to Locate Substrings

python string find: Learn how str.find() locates substrings in Python, its return value semantics, start/end parameters, and how it compares to index(), in, and re.sea...

PythonString MethodsSubstring Searchstr.findText Processing
Python string find method locating a substring within a text string, shown as a magnifying glass over a line of code.

python string find requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When you need to locate a substring inside a Python string, the str.find() method is the standard tool. It returns the lowest index where the substring occurs, or -1 if it is not found. This makes it useful for conditional logic where you want to know whether a substring exists and where it starts, without raising an exception.

line = "the quick brown fox" position = line.find("quick") print(position) # 4

The method is available on all string objects and works with any substring, including empty strings. Understanding its exact behavior, especially the return value and the optional parameters, helps you avoid subtle bugs in text parsing and validation code.

How str.find() Works

The signature of str.find() is str.find(sub[, start[, end]]). The sub argument is the substring you are looking for. The optional start and end parameters define a slice of the original string to search within, using the same semantics as slicing: start is inclusive, end is exclusive.

text = "abcabcabc" print(text.find("abc")) # 0 print(text.find("abc", 1)) # 3 print(text.find("abc", 1, 6)) # 3

The method scans the string from left to right and returns the first match. If no match is found, it returns -1. This return value is consistent across all Python versions and does not raise an error for missing substrings.

Return Value Semantics and Common Pitfalls

Because find() returns -1 when the substring is absent, you can use it directly in an if statement. A common mistake is to check if text.find(sub) > 0 instead of >= 0. The former skips a match at index 0, which is a valid position.

if text.find("start") >= 0: print("found")

Another pitfall is confusing the return value with a boolean. -1 is truthy in Python, so if text.find(sub) will always be true when the substring is not found. Always compare explicitly with >= 0 or == -1.

Using start and end to Restrict the Search

The start and end parameters are useful when you need to search within a specific region of a string, such as after a known prefix or before a delimiter. They avoid creating a slice, which would copy the string and add memory overhead.

log_line = "ERROR: disk full" if log_line.find("disk", 6) >= 0: print("disk-related error")

If start is negative, it is treated as 0. If end is greater than the string length, it is treated as the string length. The behavior matches the slicing rules, so you can rely on the same boundary handling.

Differences Between find() and index()

The str.index() method behaves like find() but raises a ValueError when the substring is not found. This changes the error-handling strategy: find() is appropriate when absence is a normal condition, while index() is useful when a missing substring indicates a malformed input that should fail loudly.

MethodMissing substring behaviorUse case
find()Returns -1Conditional checks, optional substrings
index()Raises ValueErrorRequired substrings, input validation
# find() for optional parsing pos = data.find("\n") if pos != -1: line = data[:pos] # index() for required format pos = data.index(":") # raises if missing

Choosing between them depends on whether the substring is expected to exist. If you need to handle the absence explicitly, find() avoids a try/except block.

Performance and Runtime Behavior

The find() method performs a linear scan of the string in the worst case, with a time complexity of O(n) for the length of the searched region. It does not use regular expressions, so it avoids the overhead of pattern compilation and matching. For simple literal substring searches, find() is generally faster than re.search() because no regex engine is involved.

However, find() does not support pattern matching. If you need case-insensitive search, you must either normalize the string with .lower() or .casefold() before calling find(), or use a regex with the re.IGNORECASE flag. Normalizing copies the string, which adds memory and time. For repeated searches on the same string, consider storing the normalized version once.

# Case-insensitive search if text.lower().find("error") >= 0: print("error found")

Memory usage is minimal because find() does not create intermediate slices unless you use the start and end parameters, which only limit the scan range. The method itself does not allocate additional data structures.

When to Use find() vs in vs re.search

Python offers several ways to search for substrings, and the right choice depends on what you need beyond a simple index.

  • Use in when you only need a boolean result and do not care about the position.
  • Use find() when you need the index of the first occurrence and want to handle absence without exceptions.
  • Use re.search() when you need pattern matching, such as wildcards, character classes, or alternation.
# Boolean check if "error" in text: pass # Index needed pos = text.find("error") # Pattern needed import re if re.search(r"error\s+\d+", text): pass

For a single literal substring, find() is more direct than re.search(). For multiple different substrings, a regex with alternation might be more efficient because it scans the string once, whereas multiple find() calls each scan independently. The tradeoff is that regex compilation adds upfront cost, so it only pays off when the pattern is reused or the string is long.

Edge Cases: Empty Substring and Overlapping Matches

find() with an empty substring returns the start value (or 0 if not specified), because an empty string is considered to exist at every position. This can lead to unexpected behavior if you do not guard against it.

print("abc".find("")) # 0 print("abc".find("", 2)) # 2

Overlapping matches are not considered because find() returns the first occurrence and does not provide a way to continue from the end of the match. To find all occurrences, you need to loop manually, advancing the start position past the previous match.

text = "aaaa" start = 0 while True: pos = text.find("aa", start) if pos == -1: break print(pos) start = pos + 1 # allows overlapping

This loop prints 0, 1, and 2. If you set start = pos + 2, you get non-overlapping matches. The choice depends on whether overlapping matches are relevant to your use case.

Compatibility and Version Notes

The str.find() method has been part of Python since the early versions and its behavior is stable across Python 2 and Python 3. The only notable difference is that in Python 3, strings are Unicode by default, so find() operates on code points rather than bytes. For byte strings, the bytes.find() method works similarly. This distinction matters when dealing with multibyte characters: the index returned is the character offset, not the byte offset. If you need byte positions, you must encode the string to bytes first and use bytes.find().

s = "café" print(s.find("é")) # 3, character index b = s.encode("utf-8") print(b.find(b"\xc3\xa9")) # 3, byte index (same here because é is 2 bytes)

For most text processing, the character index is the correct choice. Only when interfacing with binary protocols or file offsets should you convert to bytes.

python string find: Practical Usage and Code Examples | RYUSLOG DEV