Hit IndexError: string index out of range while parsing text in Python? You’re in good company — it’s one of the top three IndexError variants that BSIT students see in their first month of coding, right next to its cousin list index out of range.
Strings in Python behave like sequences of single characters, and they follow the same zero-indexed rules as lists.
That’s both the good news (the patterns transfer) and the bad news (the bugs transfer too). This guide walks through all 6 common causes with working code examples, plus the one trick that hides the bug instead of fixing it: slicing.

📌 Quick answer: IndexError: string index out of range means you tried to access a character in a string using a position that doesn’t exist. Strings are zero-indexed in Python — a string of length N has valid indexes 0 through N-1. Accessing s[N] raises this error. The fixes: check len(s) before indexing, guard against empty strings with if s:, and prefer iteration (for ch in s:) over manual index loops. Note: slicing (s[5:10]) never raises this error — it silently returns an empty string instead.
What “String Index Out of Range” Actually Means
Every Python string is a sequence of characters, indexed from 0. The first character lives at index 0, the second at 1, and the last at index len(s) - 1.
Asking for an index equal to or greater than len(s) raises IndexError: string index out of range.
Here’s the simplest demonstration:
name = "Ana" # length = 3, valid indexes: 0, 1, 2
print(name[0]) # ✅ "A"
print(name[2]) # ✅ "a"
print(name[3]) # ❌ IndexError: string index out of range
The string "Ana" has 3 characters. Index 3 would be the 4th character — which doesn’t exist. Same rule as lists: len(s) - 1 is the highest legal index.
Cause #1 — Accessing string[len(s)] (Off-by-One on Strings)
This is the most basic form. It usually appears when you hardcode an index that matches the length, or when you compute an index that’s one too big.
word = "Python"
print(word[len(word)]) # ❌ IndexError — len() is 6, max valid index is 5
print(word[6]) # ❌ same error, just written differently
The “len(s) trap” usually appears when a beginner thinks “I want the last character — that’s at position len(s).” It isn’t. The last character is at len(s) - 1, or more cleanly, at -1.
The fix:
word = "Python"
# ✅ Last character — the Pythonic way
print(word[-1]) # "n"
# ✅ Or use len(s) - 1
print(word[len(word) - 1]) # "n"
# ✅ For computed indexes, always bounds-check first
i = 6
if i < len(word):
print(word[i])
else:
print(f"Index {i} out of range. String length is {len(word)}.")
Cause #2 — Empty String Access (string[0] on Empty Input)
This one bites every Python beginner who reads from a file, an API, or user input. An empty string has length 0, which means it has no valid indexes at all — not even 0:
def first_letter(name):
return name[0].upper() # ❌ IndexError if name is ""
first_letter("") # crashes
first_letter(input("Name: ")) # crashes if user just presses Enter
This is the single most common cause of string index out of range in real-world code. Every input() call, every line.strip() from a file, every JSON field can be empty.
The fix — always truthiness-check first:
def first_letter(name):
if not name: # empty string is falsy
return ""
return name[0].upper()
# ✅ Or use a default value when accessing
def first_letter(name):
return name[0].upper() if name else ""
# ✅ For deeper safety, validate at the boundary
name = input("Name: ").strip()
if not name:
print("Name cannot be empty.")
else:
print(f"Hello, {name[0].upper()}{name[1:]}!")
The pattern if my_string: is true only when the string has at least one character. It’s cleaner than if len(my_string) > 0: and Pythonic.
Cause #3 — Looping Char-by-Char with range(len(s)+1)
Classic off-by-one error, identical in shape to the list version. You loop with range(len(s) + 1) by mistake, thinking the +1 includes the last character — but range(len(s)) already does that:
text = "hello"
# ❌ Wrong — the +1 sends i one past the end
for i in range(len(text) + 1):
print(text[i]) # IndexError when i == 5
# ❌ Also wrong — starts at 1, skips the first char AND overshoots
for i in range(1, len(text) + 1):
print(text[i]) # IndexError when i == 5
The fix — iterate directly:
# ✅ Direct iteration — no indexes, no off-by-one possible
for ch in text:
print(ch)
# ✅ Need the index AND the character? Use enumerate()
for i, ch in enumerate(text):
print(f"{i}: {ch}")
# ✅ Need just the length range? Use range(len(s)) — NOT +1
for i in range(len(text)):
print(text[i])
Iterating a string is one of the few places Python beginners over-engineer. for ch in text: is the answer 90% of the time.
Cause #4 — Negative Index Beyond String Length
Negative indexes count from the end: s[-1] is the last character, s[-2] the second-to-last. But go one step too far — past the start — and you hit IndexError:
word = "cat" # length 3, valid negative indexes: -1, -2, -3
print(word[-1]) # ✅ "t"
print(word[-3]) # ✅ "c"
print(word[-4]) # ❌ IndexError: string index out of range
This usually happens when code subtracts a computed offset from -1 without validating the result against the string length.
The fix — validate negative indexes the same way as positive:
def char_from_end(s, offset):
# offset=1 means last char, offset=2 means 2nd-to-last, etc.
i = -offset
if -len(s) <= i < len(s):
return s[i]
return None # or raise a clearer error
print(char_from_end("cat", 1)) # "t"
print(char_from_end("cat", 4)) # None — safe instead of crashing
One subtle rule worth memorizing: for any string of length N, valid indexes are 0 to N-1 (positive) and -1 to -N (negative). Anything outside that range raises IndexError.
Cause #5 — Slicing With Computed Indexes (The Sneaky One)
Here’s the trick Python plays with strings: slicing never raises IndexError. It silently returns whatever it can — often an empty string. Compare:
word = "hello"
# ❌ Indexing — strict, raises IndexError
print(word[10]) # IndexError: string index out of range
# ✅ Slicing — forgiving, just returns ""
print(word[10:20]) # "" (empty string, no error)
print(word[10:]) # ""
print(word[:100]) # "hello" — caps at the actual end
print(word[-100:]) # "hello" — caps at the actual start
This is by design — slicing in Python is meant to be tolerant so you can write code like preview = text[:100] without worrying whether text has 100 characters. But that same tolerance hides bugs: code that looks like it returns the first 5 characters might silently return "" if the input is empty or the computed index is wrong.
The fix — be explicit when emptiness matters:
def get_prefix(text, n):
prefix = text[:n]
if not prefix:
raise ValueError(f"Cannot get prefix of {n} from empty/short input")
return prefix
# ✅ Or convert silent failure to a loud one
def first_n_chars(text, n):
if len(text) < n:
raise ValueError(f"Need at least {n} chars, got {len(text)}")
return text[:n]
Rule of thumb: if you need a specific character, use indexing — IndexError will tell you when something’s wrong. If you need a substring and “as much as available” is acceptable, use slicing. Don’t slice when you mean to index.
Cause #6 — User Input Parsing (split() and accessing [0]/[1])
The deadliest pattern in production Python — splitting user input and grabbing positional elements without validating:
def parse_command(line):
parts = line.split()
cmd = parts[0] # ❌ IndexError if line is "" or whitespace
arg = parts[1] # ❌ IndexError if user typed only the command
return cmd, arg
parse_command("") # crashes
parse_command("login") # crashes — no second argument
parse_command("login admin") # ✅ works
Same pattern shows up when reading CSV lines, parsing JSON fields, processing form submissions, or splitting filenames. Every .split() can return a list with fewer items than you expect.
The fix — validate length before accessing, or use unpacking with defaults:
# ✅ Explicit length check
def parse_command(line):
parts = line.split()
if len(parts) < 2:
return None, None
return parts[0], parts[1]
# ✅ Unpacking with maxsplit + default
def parse_command(line):
parts = line.split(maxsplit=1)
cmd = parts[0] if parts else ""
arg = parts[1] if len(parts) > 1 else ""
return cmd, arg
# ✅ Pattern matching (Python 3.10+) — cleanest for shape-based parsing
def parse_command(line):
match line.split():
case [cmd, arg, *_]:
return cmd, arg
case [cmd]:
return cmd, ""
case _:
return "", ""
If you’re parsing dictionary-shaped data and hit KeyError instead of IndexError, see our KeyError category — same family of bug, different data structure.
Quick Prevention Checklist
To stop hitting string index out of range in future code, build these habits:
- Truthiness-check before indexing:
if s:befores[0], every time - Use
-1for the last character — notlen(s) - 1, neverlen(s) - Iterate directly:
for ch in s:instead offor i in range(len(s)): - Use
enumerate()when you need indexes:for i, ch in enumerate(s): - Validate
split()output:if len(parts) >= 2:beforeparts[1] - Know the slicing trap:
s[10:20]never errors — if you need a specific character, use indexing - Validate user input at the boundary — empty strings sneak in from
input(), files, API responses, and form fields
When You Should Use try/except IndexError
The same rule applies as with lists: try/except IndexError is appropriate at system boundaries (user input, file I/O, API responses) where you can’t predict the input.
It’s wrong inside your own business logic — that’s where bounds checks belong.
# ✅ Good — parsing untrusted external input
def safe_initial(name):
try:
return name[0].upper()
except IndexError:
return "?"
# ❌ Bad — hides a real logic bug in your own code
def get_middle_char(word):
try:
return word[len(word) // 2]
except IndexError:
return "" # hides the fact that you passed an empty string
Prefer prevention over recovery. if s: communicates intent clearly; try/except communicates “this can fail in mysterious ways.”
Frequently Asked Questions
What does “string index out of range” mean in Python?
IndexError: string index out of range. The same rule applies to negative indexes — going past -N raises the same error.Why does my empty string raise IndexError on s[0]?
"" has length 0, which means it has zero valid indexes — not even index 0. Always check if s: before accessing s[0]. This is the most common cause of string IndexError in real code, especially when reading from input(), files, or API responses where empty values are possible.How do I get the last character of a Python string safely?
s[-1] — it’s the Pythonic way to get the last character. But it still raises IndexError if the string is empty, so always check first: last = s[-1] if s else None. Avoid s[len(s)] (off by one — raises IndexError every time) and prefer s[-1] over s[len(s) - 1] for clarity.Why doesn’t string slicing raise IndexError?
s[10:20] returns an empty string instead of raising an error if the indexes are out of range. This is a feature for code like preview = text[:100] that should work even on short input. But it can hide bugs — if you need a specific character or want to catch missing data, use indexing (s[10]) instead of slicing (s[10:11]).Why does my for loop give string index out of range?
range(len(s) + 1) — the +1 sends the loop one index past the end. Use range(len(s)) instead. Better: skip indexes entirely with for ch in s:, or use for i, ch in enumerate(s): when you need both. Direct iteration is impossible to get wrong.How is this different from “list index out of range”?
IndexError when you access an invalid position. The error message changes based on the type — string index out of range vs list index out of range — but the root cause and fixes are identical. See our list index out of range guide for the list-specific patterns.How do I safely parse user input that might be too short?
parts = line.split(), check if len(parts) >= 2: before reading parts[1]. For cleaner code in Python 3.10+, use structural pattern matching: match parts: case [cmd, arg, *_]: .... This shape-based approach catches missing arguments without try/except.Final Recommendation
If you take only one habit from this guide, make it this: truthiness-check every string before you index into it. The single line if s: prevents the most common form of string index out of range in real Python code — the one that creeps in from input(), file reads, and API responses.
For loop-based bugs, drop range(len(s)) entirely and iterate directly with for ch in s:. For the slicing trap, remember the rule: indexing is strict, slicing is forgiving — pick the one that matches your intent.
And reserve try/except IndexError for system boundaries, never as a band-aid inside your own logic.
- Audit your code for unchecked
s[0]patterns — wrap withif s:guards - Replace any
range(len(s))loops with direct iteration orenumerate() - If you also hit list index out of range, the same prevention rules apply
- Browse more IndexError fixes, the KeyError category, or our full Python tutorial
Still stuck on a specific IndexError? Drop the exact error message and code snippet in the comments — we’ll help you debug it.
