PythonMastery
reference 3 min read · lesson 13 of 45 in Errors

IndexError: string index out of range

1 · The lesson

read

What this error means

You indexed into a string at a position that doesn't exist. Strings, like lists, are zero-indexed sequences: "abc"[0] is 'a', "abc"[2] is 'c', and "abc"[3] raises IndexError. An empty string "" has no valid indices at all.

When you see it

python
>>> name = "Margaret"
>>> name[10]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: string index out of range

Why it happens

It almost always shows up in parsing code that assumes a fixed structure — a date string that is always YYYY-MM-DD, a postcode that is always 6 characters, a filename that always has an extension. When the input deviates by even one character, the assumed index lands past the end.

How to fix it

1. Validate length first:

python
def first_letter(s):
    if len(s) == 0:
        return ""
    return s[0]

2. Use slicing — slices never raise:

python
year = date_str[:4]      # safe even if date_str is shorter
suffix = filename[-3:]   # safe on any string
+ setup added so this can run · defines date_str, filename
date_str = ["alpha", "beta", "gamma"]
filename = ["alpha", "beta", "gamma"]

3. Use str methods designed for the job rather than hand-rolled indexing:

python
# brittle
ext = filename[filename.index(".") + 1:]

# robust
import os
ext = os.path.splitext(filename)[1].lstrip(".")
+ setup added so this can run · defines filename
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

filename = _AutoMock('filename')

4. Catch when parsing genuinely unknown input:

python
try:
    initial = user_input[0].upper()
except IndexError:
    initial = "?"
+ setup added so this can run · defines user_input
user_input = ["alpha", "beta", "gamma"]

When you'd actually see this in real code

  • Reading a CSV where one row has an empty trailing field: row[0][0] blows up because row[0] is "".
  • Parsing a phone number assuming a country code prefix: number[1:4] is fine, but number[0] raises on an empty string.
  • Walking through a string character-by-character with an explicit counter that increments past the end inside a while loop. Prefer for ch in s: or enumerate(s).

See Also