2025, Dec 06 21:00

How to Prevent IndexError in Python Nested Loops When Printing the First Letter of Every Word

Learn how Python nested loops trigger IndexError when scanning strings, and fix it with bounds checks and a single-pass to print the first letter of each word.

Printing the first letter of every word sounds trivial, yet nested loop control can bite even in this tiny task. A common pattern is to scan a string, detect spaces, and emit the next non-space character. The pitfall appears when index updates in an inner loop outrun the outer loop’s boundary check, leading to an IndexError.

Reproducing the issue

The following program prints the first character of each word by stepping through the string and toggling a state flag. It fails with an IndexError in the inner loop when the index moves past the end of the string.

line_text = "Humpty Dumpty sat on a wall"
latch = False
ptr = 0
while ptr < len(line_text):
    if latch == False and line_text[ptr] != " ":
        print(line_text[ptr])
        ptr += 1
        latch = True
    else:
        latch = True
    while latch == True:
        if line_text[ptr] == " ":
            latch = False
            ptr += 1
            break
        else:
            ptr += 1

What actually goes wrong

The failure happens in the inner while block. Consider a string of length 27 and the moment when the index equals 26. The else branch inside the inner loop increments the index to 27 and then immediately reiterates the inner loop. The outer guard while ptr < len(line_text) is not re-evaluated until the inner loop finishes, so the very next access tries to read line_text[27], which is out of range, and the program crashes at the check that compares the current character with a space.

The core insight is that bounds checks of the outer loop do not automatically protect inner loops. Once the index goes out of range inside the nested loop, the outer condition is never consulted before the next dereference.

The minimal fix

A direct way to make the original approach safe is to include the boundary check in the inner loop’s condition as well.

line_text = "Humpty Dumpty sat on a wall"
latch = False
ptr = 0
while ptr < len(line_text):
    if latch == False and line_text[ptr] != " ":
        print(line_text[ptr])
        ptr += 1
        latch = True
    else:
        latch = True
    while latch == True and ptr < len(line_text):
        if line_text[ptr] == " ":
            latch = False
            ptr += 1
            break
        else:
            ptr += 1

This preserves the logic and prevents the out-of-range access by halting the inner scan when the index reaches the string length.

A cleaner single-pass scan

There is a simpler pattern that avoids the nested loop entirely: carry a flag that marks whether the previous character was a space. On every character, either flip the flag for spaces or print the character if the flag says we are at a word boundary, then clear it.

src = "Humpty Dumpty sat on a wall"
seen_space = True
posn = 0
while posn < len(src):
    if src[posn] == " ":
        seen_space = True
    elif seen_space:
        print(src[posn])
        seen_space = False
    posn += 1

This yields the same output without an inner loop and with less state to juggle. It also eliminates the possibility of skipping the outer boundary check.

A variant that mirrors the original logic

If you prefer to keep the same conceptual track/emit flow, you can fold it into a single loop with a state variable that flips on non-space characters and resets on spaces.

text = "Humpty Dumpty sat on a wall"
pause = False
k = 0
while k < len(text):
    if pause:
        pause = text[k] != " "
    elif text[k] != " ":
        print(text[k])
        pause = True
    k += 1

Why this matters

Nested control flow often masks implicit assumptions about loop boundaries. Here, the expectation that the outer while would guard the inner loop wasn’t true, and a single increment past the end turned into an IndexError. Being aware of where boundary checks are enforced is essential to avoid off-by-one errors and out-of-range access.

Two practical habits help spot issues like this quickly. First, inspect the critical values right before the failing line with a simple print(ptr, len(line_text)) to confirm what the code is about to do. Second, step through the program with a debugger and pay special attention to the moment when the index reaches len(string) - 1, or immediately after the last space in the input, to see how the state and index evolve.

Conclusion and practical advice

When scanning strings character-by-character, ensure every loop that dereferences an index enforces its own bounds check. If your logic uses a state flag, consider structuring the loop into distinct branches for each state rather than nesting loops; this keeps control flow predictable. Favor idiomatic expressions such as not flag instead of flag == False to reduce visual clutter and potential mistakes. And for reproducible failures, test with an explicit string rather than interactive input so the behavior stays stable while you iterate. With these small adjustments, the task of emitting the first letter of every word becomes both reliable and easy to maintain.