Python Find All Instances Of Substring

5 min read

Finding every occurrence of a specific sequence of characters within a larger string is a fundamental task in text processing, data cleaning, and parsing. In Python, there are several idiomatic ways to achieve this, ranging from simple built-in methods to powerful regular expressions and modern standard library additions. Understanding the nuances of each approach—specifically regarding performance, readability, and the type of data returned—allows developers to choose the right tool for their specific use case.

The Classic Approach: Using str.find() in a Loop

The most basic mechanism for locating a substring is the str.Think about it: find(sub[, start[, end]]) method. Because of that, it returns the lowest index where the substring is found, or -1 if it is absent. To find all instances, you must wrap this in a loop, updating the start index after every match.

text = "abracadabra"
substring = "abra"
indices = []
start = 0

while True:
    index = text.find(substring, start)
    if index == -1:
        break
    indices.append(index)
    start = index + 1  # Move past the current match

print(indices)  # Output: [0, 7]

Key Considerations:

  • Overlapping Matches: The snippet above uses start = index + 1. This allows finding overlapping matches (e.g., finding "ana" in "banana" yields indices 1 and 3). If you want non-overlapping matches, use start = index + len(substring).
  • Performance: This is implemented in C and is reasonably fast for single-pass scanning, but the Python-level while loop introduces overhead for strings with a massive number of matches.

The Pythonic Generator: str.index() and Exceptions

While find returns -1 on failure, str.Day to day, index() raises a ValueError. Some developers prefer the "Easier to Ask for Forgiveness than Permission" (EAFP) style using a try/except block. Still, for finding all instances, this is generally considered less readable and slower than the find loop due to the cost of exception handling in a tight loop.

Modern Standard Library: re.finditer() for Power and Flexibility

The re module is the heavy lifter for string searching. end()), the matched string (match.Even so, the re. Consider this: this is often the preferred "professional" approach because match objects contain rich metadata: the start index (match. finditer(pattern, string) function returns an iterator yielding **match objects** for all non-overlapping matches. In real terms, start()), end index (match. group()), and captured groups.

No fluff here — just what actually works.

import re

text = "The rain in Spain falls mainly on the plain."
pattern = r"ain"

matches = [(m.Here's the thing — start(), m. group()) for m in re.

### Handling Overlapping Matches with Regex
Standard regex engines consume characters as they match, preventing overlapping matches by default. To find overlapping instances (e.g., finding "aba" in "ababa" at indices 0 and 2), you must use a **positive lookahead assertion** `(?=...)`. This asserts a match without consuming characters.

```python
import re

text = "ababa"
pattern = r"(?=aba)"  # Lookahead for overlapping matches

indices = [m.start() for m in re.finditer(pattern, text)]
print(indices)  # Output: [0, 2]

Performance Note on re

Compiling the pattern once using re.compile() is significantly faster if you are searching the same pattern across multiple strings or in a tight loop Not complicated — just consistent. Turns out it matters..

compiled_pattern = re.compile(r"\d+")  # Compile once
# ... later in code ...
for match in compiled_pattern.finditer(large_text):
    process(match)

The New Contender: str.finditer() (Python 3.11+)

Introduced in **Python 3.finditer(sub[, start[, end]])brings the convenience of an iterator interface to the built-in string type without importing theremodule. 11**,str.It returns an iterator yielding slice objects (or tuples in some implementations, specifically slice objects representing the span) for non-overlapping occurrences.

Note: As of Python 3.12, str.finditer yields slice objects.

# Python 3.11+
text = "hello world, hello universe"
substring = "hello"

# Returns an iterator of slice objects
for s in text.finditer(substring):
    print(f"Found at [{s.start}:{s.stop}] -> '{text[s]}'")

# Output:
# Found at [0:5] -> 'hello'
# Found at [13:18] -> 'hello'

Why use this over re.finditer?

  1. Zero Dependencies: No import re required.
  2. Literal String Search: It searches for the exact substring, not a regex pattern. You don't need to escape special characters like ., *, ?, [, ].
  3. Speed: For literal substring searching, the highly optimized C implementation (often using algorithms like Two-Way or Boyer-Moore-Horspool) is significantly faster than the regex engine.

Limitation: It does not support overlapping matches natively (it advances past the match) and lacks the advanced capture group features of regex.

Functional Style: List Comprehension with enumerate

For developers who prefer a functional, one-liner style without importing modules, combining enumerate with slicing or startswith is a clever trick.

text = "mississippi"
sub = "issi"

# Using startswith (cleaner than slicing manually)
indices = [i for i in range(len(text)) if text.startswith(sub, i)]
print(indices)  # Output: [1, 4]

Pros: Extremely concise; handles overlapping matches naturally. Cons: O(N * M) complexity in the worst case (where N is text length, M is substring length) because startswith checks the substring at every single index. For long strings or long substrings, this is orders of magnitude slower than find or re which use optimized search algorithms (O(N+M)).

Third-Party Powerhouses: regex and more-itertools

When the standard library isn't enough, two packages are industry standards.

The regex Module (Drop-in re Replacement)

The third-party regex module (install via pip install regex) supports overlapping matches natively via the overlapped=True flag in finditer. It also supports fuzzy matching and variable-width lookbehinds Not complicated — just consistent..

# pip install regex
import regex

text = "ababa"
# Native overlapping support
matches = [m.start() for m in regex.finditer("aba", text, overlapped=True)]
print(matches)  # Output: [0, 2]

The more-itertools Library

This library provides locate and windowed utilities that can be composed for complex stream processing, though for simple substring indexing, regex or re are usually more direct.

Comparing Performance: A Quick Mental Model

Method Overlapping Support Speed (Literal Substring) Returns Best For
while + str.find Manual (+1 vs +len) Fastest (C-loop) List of int (indices) Simple scripts, max speed, no imports.
Brand New

Current Topics

Worth Exploring Next

Readers Loved These Too

Thank you for reading about Python Find All Instances Of Substring. We hope the information has been useful. Feel free to contact us if you have any questions. See you next time — don't forget to bookmark!
⌂ Back to Home