Fix: Go panic: runtime error: index out of range
Quick Answer
How to fix Go panic runtime error index out of range caused by empty slices, off-by-one errors, nil slices, concurrent access, and missing bounds checks.
The Error
Your Go program crashes with:
panic: runtime error: index out of range [3] with length 3Or variations:
panic: runtime error: index out of range [0] with length 0panic: runtime error: index out of range [-1]panic: runtime error: slice bounds out of range [5:3]goroutine 1 [running]:
main.main()
/app/main.go:15 +0x1aYou accessed a slice or array element at an index that does not exist. Go panics at runtime because it performs bounds checking on every array/slice access.
Why This Happens
Go slices and arrays are zero-indexed. A slice of length 3 has valid indices 0, 1, and 2. Accessing index 3 or higher, or a negative index, triggers a panic.
Unlike C, Go does not allow out-of-bounds memory access. Every index operation is bounds-checked at runtime. This prevents memory corruption but causes panics if your code has index bugs.
Common causes:
- Empty slice. Accessing
slice[0]when the slice has no elements. - Off-by-one error. Using
len(slice)as an index instead oflen(slice)-1. - Wrong loop bounds. Looping with
i <= len(slice)instead ofi < len(slice). - Missing length check. Not verifying the slice has enough elements before accessing.
- Concurrent modification. Another goroutine shrinks the slice while you access it.
- Nil slice. A nil slice has length 0, so any index access panics.
Fix 1: Check Length Before Accessing
Always verify the slice has enough elements:
Broken:
func getFirst(items []string) string {
return items[0] // Panics if items is empty!
}Fixed:
func getFirst(items []string) string {
if len(items) == 0 {
return "" // Return zero value for empty slice
}
return items[0]
}For accessing any index:
func getAt(items []string, index int) (string, bool) {
if index < 0 || index >= len(items) {
return "", false
}
return items[index], true
}
// Usage:
value, ok := getAt(mySlice, 5)
if ok {
fmt.Println(value)
}Pro Tip: Follow the Go pattern of returning a value and a boolean (
value, ok) for operations that can fail. This mirrors how map lookups and type assertions work in Go and makes error handling explicit.
Fix 2: Fix Off-By-One Errors
The most classic programming error:
Broken — accessing index equal to length:
items := []string{"a", "b", "c"}
// items[3] does NOT exist — valid indices are 0, 1, 2
last := items[len(items)] // panic: index out of range [3] with length 3Fixed:
last := items[len(items)-1] // Index 2 — the last elementBroken — wrong loop condition:
for i := 0; i <= len(items); i++ { // <= includes len(items), which is out of bounds
fmt.Println(items[i])
}Fixed:
for i := 0; i < len(items); i++ { // < stops before len(items)
fmt.Println(items[i])
}
// Even better — use range
for i, item := range items {
fmt.Println(i, item)
}Use range whenever possible. It handles bounds automatically and is the idiomatic Go way to iterate:
for _, item := range items {
fmt.Println(item)
}Common Mistake: Using
i <= len(slice)in aforloop. In Go (and most languages), the last valid index islen(slice) - 1. Use<not<=for the loop condition, or better yet, userange.
Fix 3: Fix Slice Bounds in Sub-Slicing
Sub-slice operations also panic on invalid bounds:
data := []int{1, 2, 3, 4, 5}
// Panic: slice bounds out of range [6:5]
sub := data[6:]
// Panic: slice bounds out of range [2:8]
sub := data[2:8]Fixed — validate bounds:
func safeSlice(data []int, start, end int) []int {
if start < 0 {
start = 0
}
if end > len(data) {
end = len(data)
}
if start > end {
return nil
}
return data[start:end]
}Common sub-slice patterns:
// First N elements (capped at length)
func firstN(data []int, n int) []int {
if n > len(data) {
n = len(data)
}
return data[:n]
}
// Last N elements (capped at length)
func lastN(data []int, n int) []int {
if n > len(data) {
n = len(data)
}
return data[len(data)-n:]
}Fix 4: Handle Nil and Empty Slices
A nil slice has length 0. Accessing any index panics:
var items []string // nil slice
fmt.Println(len(items)) // 0 — safe
fmt.Println(items[0]) // panic!
items = []string{} // empty (non-nil) slice
fmt.Println(len(items)) // 0 — safe
fmt.Println(items[0]) // panic! Same as nil sliceGuard all index access:
func processItems(items []string) {
if len(items) == 0 {
fmt.Println("No items to process")
return
}
first := items[0]
last := items[len(items)-1]
fmt.Printf("First: %s, Last: %s\n", first, last)
}Nil slices are safe for append, len, cap, and range:
var items []string // nil
items = append(items, "hello") // Safe — append handles nil
fmt.Println(len(items)) // Safe — returns 0 for nil
for _, item := range items { // Safe — iterates 0 times for nil
fmt.Println(item)
}Fix 5: Fix Map-Based Index Lookups
When you use a map value as an index:
indices := map[string]int{"a": 0, "b": 1, "c": 2}
data := []string{"x", "y", "z"}
// If key doesn't exist, map returns 0 — might be a valid index by accident
idx := indices["d"] // Returns 0 (zero value), not an error
fmt.Println(data[idx]) // Prints data[0] — wrong but no panic
// Fix: Check if the key exists
idx, ok := indices["d"]
if !ok {
fmt.Println("Key not found")
return
}
fmt.Println(data[idx])When the map value is used for slicing:
offsets := map[string]int{"start": 10}
data := []byte("short")
start := offsets["start"] // 10
_ = data[start:] // panic: index out of range [10] with length 5Always validate map values before using them as indices.
Fix 6: Fix Concurrent Slice Access
Goroutines can cause index panics when one shrinks a slice while another reads it:
Broken:
var data []int
go func() {
for {
if len(data) > 0 {
data = data[1:] // Shrink the slice
}
}
}()
go func() {
for {
if len(data) > 0 {
_ = data[0] // May panic if other goroutine shrinks data between check and access
}
}
}()Fixed — use a mutex:
var (
data []int
mu sync.Mutex
)
go func() {
for {
mu.Lock()
if len(data) > 0 {
data = data[1:]
}
mu.Unlock()
}
}()
go func() {
for {
mu.Lock()
if len(data) > 0 {
_ = data[0]
}
mu.Unlock()
}
}()Fixed — use channels instead of shared slices:
ch := make(chan int, 100)
// Producer
go func() {
for _, v := range items {
ch <- v
}
close(ch)
}()
// Consumer
for v := range ch {
process(v)
}Fix 7: Recover from Panics
For server applications, recover from panics to avoid crashing the entire process:
func safeHandler(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic: %v", r)
http.Error(w, "Internal Server Error", 500)
}
}()
// Your handler code that might panic
processRequest(w, r)
}As middleware:
func recoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("Panic recovered: %v\n%s", err, debug.Stack())
http.Error(w, "Internal Server Error", 500)
}
}()
next.ServeHTTP(w, r)
})
}Note: recover() only catches panics in the current goroutine. Panics in child goroutines are not caught by the parent’s defer/recover.
Fix 8: Use Helper Functions for Safe Access
Create utility functions for common safe-access patterns:
// Safe get with default value
func getOr[T any](slice []T, index int, defaultVal T) T {
if index < 0 || index >= len(slice) {
return defaultVal
}
return slice[index]
}
// Usage:
name := getOr(names, 5, "unknown")
// Safe first/last
func first[T any](slice []T) (T, bool) {
if len(slice) == 0 {
var zero T
return zero, false
}
return slice[0], true
}
func last[T any](slice []T) (T, bool) {
if len(slice) == 0 {
var zero T
return zero, false
}
return slice[len(slice)-1], true
}Still Not Working?
Use the -race flag to detect concurrent access:
go run -race main.go
go test -race ./...The race detector finds concurrent slice/map access at runtime and reports it with stack traces.
Check for index arithmetic bugs. Complex index calculations are error-prone:
// Dangerous — what if mid overflows or is negative?
mid := (low + high) / 2
// Safer
mid := low + (high-low)/2Check for empty function returns. Functions that return slices might return nil or empty slices:
results := fetchResults() // Might return nil
if len(results) > 0 {
process(results[0])
}For Go type mismatch errors, see Fix: Go cannot use X as type Y. For undefined variable errors, see Fix: Go undefined variable. For Go module issues, see Fix: Go module not found.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Go cannot find package / no required module provides package
How to fix 'cannot find package' and 'no required module provides package' errors in Go by syncing dependencies, fixing import paths, configuring private repos, and resolving workspace issues.
Fix: Go fatal error: all goroutines are asleep - deadlock!
How to fix Go fatal error all goroutines are asleep deadlock caused by unbuffered channels, missing goroutines, WaitGroup misuse, and channel direction errors.
Fix: Go cannot use X (type Y) as type Z in argument
How to fix Go 'cannot use as type' error caused by type mismatches, interface satisfaction, pointer vs value receivers, type conversions, and generic constraints.
Fix: Go undefined: variable (or function)
How to fix Go undefined error caused by undeclared variables, wrong package scope, unexported names, missing imports, build tags, and file organization issues.