Understanding the Panic
The index out of range panic occurs when you access a slice or array element at an invalid position:
``` panic: runtime error: index out of range [5] with length 3
goroutine 1 [running]: main.main() /app/main.go:15 +0x1b ```
The message shows the invalid index you tried to access (5) and the actual length (3).
Common Scenarios and Fixes
Scenario 1: Direct Index Access
Problem code:
``go
func getItem(items []string, index int) string {
return items[index] // Panics if index >= len(items)
}
Safe version:
``go
func getItem(items []string, index int) (string, error) {
if index < 0 || index >= len(items) {
return "", fmt.Errorf("index %d out of bounds for slice of length %d", index, len(items))
}
return items[index], nil
}
Scenario 2: Loop with Fixed Index
Problem code:
``go
func processBatch(records []Record) {
for i := 0; i <= len(records); i++ { // Bug: <= should be <
process(records[i])
}
}
Fixed version:
``go
func processBatch(records []Record) {
for i := 0; i < len(records); i++ { // Correct: <
process(records[i])
}
}
Or use range for clarity:
``go
func processBatch(records []Record) {
for _, record := range records {
process(record)
}
}
Scenario 3: Slicing Beyond Capacity
Problem code:
``go
data := []int{1, 2, 3}
subset := data[1:5] // Panic: slice bounds out of range [1:5]
Safe slicing:
``go
func safeSlice(data []int, start, end int) []int {
if start < 0 {
start = 0
}
if end > len(data) {
end = len(data)
}
if start >= end {
return []int{}
}
return data[start:end]
}
Or use Go 1.21's min function:
``go
data := []int{1, 2, 3}
end := min(5, len(data))
subset := data[1:end] // [2, 3]
Scenario 4: Accessing After Append
This is a subtle bug that can cause issues:
Problem code: ```go func main() { items := make([]int, 0, 5) items = append(items, 1, 2, 3)
// Later, someone forgets items was re-allocated fmt.Println(items[5]) // Panic! } ```
Always check length: ```go func main() { items := make([]int, 0, 5) items = append(items, 1, 2, 3)
if len(items) > 5 { fmt.Println(items[5]) } else { fmt.Println("Index not available") } } ```
Scenario 5: Empty Slice Confusion
Problem code:
``go
var results []string // nil slice
if results[0] == "" { // Panic!
// handle empty
}
Fixed version:
``go
var results []string
if len(results) == 0 {
// handle empty
}
Safe Access Helpers
Create reusable safe access functions:
```go // SafeSliceGet returns the element at index or a default value func SafeSliceGet[T any](slice []T, index int, defaultValue T) T { if index < 0 || index >= len(slice) { return defaultValue } return slice[index] }
// SafeSliceGetOK returns the element and a boolean indicating success func SafeSliceGetOK[T any](slice []T, index int) (T, bool) { var zero T if index < 0 || index >= len(slice) { return zero, false } return slice[index], true }
// Usage items := []string{"a", "b", "c"} first := SafeSliceGet(items, 0, "default") // "a" fifth := SafeSliceGet(items, 5, "default") // "default"
val, ok := SafeSliceGetOK(items, 1) // "b", true val, ok = SafeSliceGetOK(items, 10) // "", false ```
Debugging Commands
When you see the panic, the stack trace shows exactly where it happened:
```bash # Run with full stack trace GOTRACEBACK=all go run main.go
# Use delve for interactive debugging dlv debug main.go (dlv) break main.go:15 (dlv) continue (dlv) print len(items) (dlv) print index ```
Verification Tests
Write tests that specifically check boundary conditions:
```go func TestGetItem(t *testing.T) { items := []string{"a", "b", "c"}
// Valid indices for i := 0; i < len(items); i++ { got, err := getItem(items, i) if err != nil { t.Errorf("unexpected error at index %d: %v", i, err) } if got != items[i] { t.Errorf("expected %s, got %s", items[i], got) } }
// Invalid indices invalidIndices := []int{-1, len(items), len(items) + 1, 100} for _, idx := range invalidIndices { _, err := getItem(items, idx) if err == nil { t.Errorf("expected error at index %d", idx) } } } ```
Prevention with Linters
Use static analysis to catch potential issues:
```bash # Install and run staticcheck go install honnef.co/go/tools/cmd/staticcheck@latest staticcheck ./...
# Install gosec for security-related bounds checks go install github.com/securego/gosec/v2/cmd/gosec@latest gosec ./... ```
These tools can identify code paths where bounds checking might be missing.