What goes wrong
A slice with n elements has the valid indices 0 to n - 1. If you read or write at any other index, Go does not guess and does not return a default value. The program panics: it stops at that line and prints an error with a trace of where it happened.
ourArray := []int{50, 40, 30}
fmt.Println(ourArray[0]) // 50
fmt.Println(ourArray[2]) // 30
fmt.Println(ourArray[3]) // panic
fmt.Println(ourArray[30]) // panic
The first two lines print 50 and 30. The third stops the program with this message, and the fourth never runs:
panic: runtime error: index out of range [3] with length 3
The message tells you the bad index and the slice's length, which is usually all you need to find the bug. The lines that ran before the panic have already produced their output.
Run time, not compile time
With a slice, the length is not part of the type, and it can change while the program runs. For that reason the compiler generally cannot tell whether an index is too large, even a constant one such as ourArray[30]. The program builds fine and panics only when that line executes. This is different from arrays, where the length is fixed and a constant index that is too large is rejected at compile time.
One check does happen early: a negative constant index, like ourArray[-1], is a compile error, because no slice can ever have that index. A negative index that comes from a variable is caught at run time, with a message such as index out of range [-1].
The exercise
The starting code should print the last element of ages, which is 21:
ages := []int{14, 32, 50, 21}
fmt.Println(ages[len(ages)])
Instead it crashes with panic: runtime error: index out of range [4] with length 4. Read the message: the program asked for index 4, but the slice has four elements at indices 0 to 3. The expression len(ages) is the number of elements, which is one more than the last index. The fix is to subtract 1:
ages := []int{14, 32, 50, 21}
fmt.Println(ages[len(ages)-1])
21
Empty slices
The fix above has a catch. With an empty slice, len(s) - 1 is -1, so reading the last element panics with index out of range [-1]. When a slice might be empty, check first:
if len(ages) > 0 {
fmt.Println(ages[len(ages)-1])
}
The same applies to s[0]: an empty slice has no first element either.
Bounds in slice expressions
Re-slicing has bounds too. In s[:k], k must not be negative and must not exceed the slice's capacity. Removing the last element of an empty slice with s = s[:len(s)-1] panics with slice bounds out of range [:-1]. The remedy is the same check on the length. Both kinds of mistake look alike: the trace that follows the message names the line that caused it.
Avoiding the panic
- Use
len(s) - 1for the last index. - In a loop, write
i < len(s), neveri <= len(s). - Use
for i, v := range swhen you just want to visit every element: the indices it produces are always valid. - Check any computed index against
0andlen(s)before using it.
Common mistakes
- Off-by-one with the length. The last valid index is one less than
len. - Forgetting that the slice changed. After removing elements, the old last index no longer exists.
- Assuming an assignment grows the slice.
s[len(s)] = xpanics. Useappendto add an element. - Looking for an exception to catch. Go reports this as a panic, not a thrown error. Fix the index rather than handling the failure.
Key takeaways
- Valid indices for a slice are 0 to
len(s) - 1. - An index outside that range panics at run time and stops the program.
- The message names the index and the length, so read it first.
- Check
len(s)before touching the first or last element of a slice that may be empty.