You wrote matches = re.findall(pattern, text) then matches[0] and crashed with IndexError because the pattern didn’t match. re.findall returns an empty list when nothing matches, and indexing into an empty list raises IndexError.

📌 Quick answer: If you want “the first match or None,” use re.search(pattern, text) which returns None on no match, instead of re.findall(...)[0]. Check with if m := re.search(...): m.group() using the walrus operator (Python 3.8+).
Pattern 1: Use re.search for “first match or nothing”
re.search returns a Match object on success, None on failure. Much safer than indexing into findall.
import re
m = re.search(r"(\d+)", text)
if m:
number = m.group(1)
else:
number = NonePattern 2: Walrus operator (Python 3.8+)
Cleaner conditional + access in one line.
if m := re.search(r"version: (\d+\.\d+)", text):
version = m.group(1)
else:
version = "unknown"Pattern 3: Default with next()
For “any match from findall” with default.
matches = re.findall(r"\b\w+@\w+\.\w+\b", text)
first_email = next(iter(matches), None)Pattern 4: re.finditer for streaming matches
For pattern matching in a large string, finditer is lazy and yields Match objects.
for m in re.finditer(r"\d+", text):
print(m.group(), m.start(), m.end())
# Naturally handles "no matches" case (loop just doesn't execute)Prevention
- Default to re.search() for “find one”, re.findall() for “find all”
- Never index into re.findall() result without checking the list is non-empty
- Use walrus operator
:=on Python 3.8+ for cleaner conditional matching - Test with empty/non-matching inputs in unit tests
Related Guides
- List index out of range (full guide)
- String index out of range
- All IndexError fixes
- Python Tutorial hub
Frequently Asked Questions
Why does re.findall(…)[0] raise IndexError?
re.findall returns an empty list when nothing matches the pattern. Indexing into an empty list with [0] raises IndexError. Use re.search instead, which returns None on no match.
What’s the difference between re.search, re.findall, and re.finditer?
re.search returns the first Match object (or None). re.findall returns a list of all matches as strings (or tuples if groups). re.finditer returns an iterator of Match objects (lazy, memory-efficient for large texts).
Why does re.findall return tuples sometimes?
When the pattern has multiple capture groups, each match is returned as a tuple of group values. For a single group it’s a list of strings. To always get Match objects, use re.finditer or re.search instead.
How do I check if a regex matched without raising?
result = re.search(pattern, text); if result: … It returns None on no match. Or use re.match for anchored matching (only at string start).
Should I compile my regex with re.compile?
Compile once when the pattern is reused many times (loops, multi-line files). Skip compilation for one-shot uses, re internal caching handles small numbers of recent patterns. Both raise the same errors.
