Python re.findall IndexError: Empty Match Out of Range (2026)

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.

Python re.findall IndexError Empty Match Out of Range (2026)
Python re.findall IndexError Empty Match Out of Range (2026)

📌 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 = None

Pattern 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

  1. Default to re.search() for “find one”, re.findall() for “find all”
  2. Never index into re.findall() result without checking the list is non-empty
  3. Use walrus operator := on Python 3.8+ for cleaner conditional matching
  4. Test with empty/non-matching inputs in unit tests
Quick step-by-step summary (click to expand)
  1. Check the findall result length. matches = re.findall(pattern, text); if matches: first = matches[0]. Always guard positional access.
  2. Use re.search for single-match extraction. re.search returns None on no match instead of an empty list. Check with if m and use m.group().
  3. Verify your regex pattern with re.compile. re.compile shows syntax errors clearly. Test on regex101.com before running in Python.
  4. Use named groups for structured extraction. For multi-value matches, use (?P…) named groups and access with m.group(“name”) instead of positional group[0].

Why IndexError happens

List index out of range means you accessed my_list[i] beyond the list’s actual length. Python lists are indexed from 0 to len(list)-1.

Common triggers

  • Off-by-one. my_list[len(my_list)] fails — use len(my_list) - 1.
  • Empty container. my_list[0] fails when the list is empty.
  • Wrong data source. CSV had fewer columns than expected.
  • Loop range wrong. for i in range(len(my_list) + 1) — off-by-one.
  • API returned empty result. Unhandled empty response.

Diagnostic pattern

# BAD — accessing first element without check
def get_first(items):
    return items[0]     # IndexError if items is empty

# GOOD — guard for empty
def get_first(items):
    if not items:
        return None
    return items[0]

# BETTER — use Optional and let caller handle
from typing import Optional, Sequence, TypeVar
T = TypeVar("T")

def get_first(items: Sequence[T]) -> Optional[T]:
    return items[0] if items else None

# For pandas, use .iloc with .empty check
import pandas as pd
def first_row(df: pd.DataFrame) -> Optional[dict]:
    if df.empty:
        return None
    return df.iloc[0].to_dict()

# For enumerate-based loops, this is safe
for i, item in enumerate(items):
    print(i, item)      # never IndexError

# Never write: for i in range(len(items) + 1)

Best practices

  • Prefer enumerate over range(len()). Never off-by-one.
  • Guard empty containers. Return None or default before accessing.
  • Use slicing. items[:5] is safe even if items has fewer than 5 elements.
  • Use type hints with Optional. Communicates that the value may not exist.
  • Use pytest with edge cases. Test empty lists, single-element lists, off-by-one boundaries.

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.

Adrian Mercurio


Full-Stack Developer at PIES IT Solution

Specializes in building complete capstone projects with full documentation. Strong background in PHP/MySQL development and database design. Has personally built and tested over 30 capstone-ready projects with ER diagrams, DFDs, and chapter-by-chapter thesis documentation.

Expertise: PHP · Laravel · Database Design · Capstone Projects · C# · C · C++ · Python · AI Projects
 · View all posts by Adrian Mercurio →

Leave a Comment