Start learning free

LESSON 61 OF 338 · STRINGS I

Exceeding String Bounds in Go: Fixing an Index Panic

Lesson Read the full explanation

String indexes must stay inside the valid range.

For a string with length len(message), the first index is 0 and the last valid index is len(message) - 1.

message := "I love pancakes"

fmt.Println(string(message[len(message)-1])) // last character

Trying to access message[len(message)] goes past the end and will fail.

Assignment
Follow the Coding Tutorial and print the last character without exceeding the string bounds.


STEP 1 OF 1
    Ctrl + Enter to check

    GO · STRINGS I

    Exceeding String Bounds in Go, explained

    What goes wrong

    A string of length n has valid indexes 0 to n - 1. Asking for any other index is an error. In Go it shows up while the program runs, as a panic: the program prints a message and a stack trace, and stops.

    The lesson starts with code that tries to print the last character of a string but panics:

    message := "I love pancakes"
    
    fmt.Println(string(message[len(message)]))
    

    The run ends with this message:

    panic: runtime error: index out of range [15] with length 15
    

    Reading the panic message

    The message is precise. It names the index the program used, 15, and the length of the string, 15. A valid index must be smaller than the length, so 15 can't work for a string with 15 bytes. The positions are numbered 0 to 14.

    The cause is in the code: len(message) is the count of bytes, and counting starts at 1, while indexes start at 0. The count is therefore always one more than the last index. This is a classic off-by-one error.

    The fix, step by step

    1. Find the last valid index. It is the length minus one: len(message) - 1, which is 14.
    2. Use it inside the brackets. message[len(message) - 1] reads the byte for s.
    3. Keep the string(...) conversion. Indexing gives a byte, and the conversion makes it print as a letter.

    The solution in Go

    message := "I love pancakes"
    
    fmt.Println(string(message[len(message) - 1]))
    

    It prints:

    s
    

    Other ways to go out of bounds

    Indexing is not the only place where bounds matter. A slice expression has two bounds, and both are checked when the program runs:

    message := "I love pancakes"
    n := 20
    fmt.Println(message[:n])
    // panic: runtime error: slice bounds out of range [:20] with length 15
    

    Here the end bound is larger than the length. Another case is a start that comes after the end:

    s := "abc"
    a, b := 3, 1
    fmt.Println(s[a:b])
    // panic: runtime error: slice bounds out of range [3:1]
    

    Unlike indexing, a slice may use len(s) as a bound, because the end of a slice is excluded. s[len(s):] is a valid empty string, while s[len(s)] is not valid.

    Compile-time and run-time checks

    Go checks what it can before the program starts. If the index is a constant, the compiler rejects an out-of-range value on a constant string, and any negative constant index is a compile error. When the index comes from a variable or from len, as in this lesson, the compiler can't know the value, so the check happens at run time and produces the panic. Either way, Go never reads memory beyond the string. That is a safety guarantee, not something you have to test for yourself.

    Edge cases and common mistakes

    • The empty string. len("") - 1 is -1, and s[-1] panics with index out of range [-1]. Check len(s) > 0 first when the string may be empty.
    • Using the length as an index. This is the lesson's bug. Indexes end one before the length.
    • Fixing it by shrinking the string. The cure is the right index, not a different input.
    • Dropping string(...) while fixing. The program then prints 115 instead of s.

    A habit that prevents the bug

    When an index expression involves len, say out loud what it points at. len(s) is "one past the end", len(s) - 1 is "the last element", and len(s) - 2 is "the one before it". A loop that should visit every index uses i < len(s). Choosing i <= len(s) instead produces exactly the panic from this lesson on its final pass. When a panic names an index equal to the length, an off-by-one in a bound is almost always the reason.

    Key takeaways

    • Valid indexes for a string of length n are 0 to n - 1.
    • An index out of range is a run-time panic that names the index and the length.
    • The last character is s[len(s) - 1], as long as the string isn't empty.
    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free