goseverity: can-fix
runtime error: index out of range

Go index out of range — runtime error: index out of range [N] with length M

index out of range [N] with length M

97% fixable~10 mindifficulty: beginner

Verified against Go spec: Index expressions, Go spec: Slice expressions, Go blog: Arrays, slices (and strings): The mechanics of 'append' · Updated June 2026

> quick_fix

You accessed a slice, array, or string at an index that is >= its length. Go's error message tells you the exact index and the length. Check that your loop or index calculation stays within `[0, len(slice)-1]`. Always check `len(s) > 0` before accessing `s[0]`.

// Panics if items is empty
func first(items []string) string {
    return items[0]  // panic: index out of range [0] with length 0
}

// Fixed — guard against empty slice
func first(items []string) (string, bool) {
    if len(items) == 0 {
        return "", false
    }
    return items[0], true
}

What causes this error

Go slices, arrays, and strings are indexed from 0 to `len(s)-1`. Accessing index `len(s)` or beyond causes a runtime panic. Common causes: off-by-one in a loop (`i <= len(s)` instead of `i < len(s)`), assuming a slice is non-empty without checking, using a hardcoded index on a slice that could be empty, or splitting a string and assuming the result has enough parts.

> advertisementAdSense placeholder

How to fix it

  1. 01

    step 1

    Read the panic message — it tells you the index and length

    Go's index-out-of-range message is specific: 'index out of range [3] with length 3' means you accessed index 3 on a slice of length 3 (valid indices are 0, 1, 2). The stack trace shows the exact line.

    // Example panic:
    // goroutine 1 [running]:
    // main.process(...)
    //     /app/main.go:12 +0x...
    // runtime error: index out of range [3] with length 3
    // ← accessing s[3] on a 3-element slice
  2. 02

    step 2

    Fix off-by-one in loops

    The most common cause: `i <= len(s)` should be `i < len(s)`. The last valid index is `len(s)-1`, not `len(s)`.

    items := []string{"a", "b", "c"}
    
    // BAD — i goes 0, 1, 2, 3 → panic on i=3
    for i := 0; i <= len(items); i++ {
        fmt.Println(items[i])
    }
    
    // GOOD — i goes 0, 1, 2 → all valid
    for i := 0; i < len(items); i++ {
        fmt.Println(items[i])
    }
    
    // BETTER — use range which handles bounds automatically
    for _, item := range items {
        fmt.Println(item)
    }
  3. 03

    step 3

    Check for empty slices before indexing

    A nil slice and an empty slice both have length 0. Indexing either panics. Always check `len(s) > 0` before accessing any specific index.

    func process(records []Record) error {
        // BAD — panics if records is empty
        first := records[0]
    
        // GOOD — guard first
        if len(records) == 0 {
            return fmt.Errorf("no records to process")
        }
        first := records[0]
        _ = first
        return nil
    }
  4. 04

    step 4

    Validate string splits before indexing parts

    `strings.Split` always returns at least one element, but may return fewer than expected if the separator isn't in the string. Check the length of the result before indexing.

    // BAD — panics if no ':' in input
    parts := strings.Split(input, ":")
    host := parts[0]
    port := parts[1]  // panic if input has no ':'
    
    // GOOD — validate length
    parts := strings.Split(input, ":")
    if len(parts) != 2 {
        return fmt.Errorf("invalid host:port format: %q", input)
    }
    host, port := parts[0], parts[1]

How to verify the fix

  • The panic no longer occurs on the failing input.
  • Tests include an empty-slice case and the maximum-length case.
  • All loops use `i < len(s)`, not `i <= len(s)`.

Why runtime error: index out of range happens at the runtime level

Go's runtime performs bounds checking on every slice and array index operation at runtime. The check compares the index against the slice header's length field. If index >= length (or index < 0), the runtime calls `runtime.panicIndex` which throws a panic with the specific index and length values. Go's compiler can eliminate bounds checks when it can statically prove the index is valid — for example, inside a `for i := range s` loop where `i` is used to index `s`. The bounds-check elimination (BCE) pass in the Go compiler is inspectable via `gcflags="-d=ssa/check_bce"` which marks BCE sites in the output.

Common debug mistakes for runtime error: index out of range

  • Using `i <= len(s)` in a for loop — the last iteration accesses `s[len(s)]` which is out of bounds.
  • Assuming `strings.Split` returns a specific number of parts — it returns 1 element even if the delimiter isn't found.
  • Using `s[0]` in a function that accepts a variadic or optional argument — callers can pass an empty slice.
  • Accessing a slice in a goroutine after it's been re-sliced or replaced in the main goroutine — length changes under the goroutine.
  • Hardcoded indices in JSON/CSV parsing — assuming column 5 exists in every row without checking.

When runtime error: index out of range signals a deeper problem

Frequent index-out-of-range panics in a codebase indicate missing input validation at function boundaries. Functions that accept slices should document whether they require non-empty input and enforce it with an early return or error. The Go idiom is explicit: guard conditions at the top of functions using `if len(s) == 0 { return ..., err }`. Codebases that parse external data (CSV files, JSON arrays, query results) without length validation after every parse are especially vulnerable — external data never has guaranteed shape. Adding table-driven tests that include empty-input and single-element cases catches this class of bug before it reaches production.

Editor's take

The index-out-of-range panic has a distinctive production signature: it only appears on specific inputs. An API that processes user-uploaded CSV files works fine in testing (where test files are well-formed) and panics in production (where real user files are missing columns, have empty rows, or have unexpected formatting). The bug hides until a real user triggers it.

The Go idiom that prevents most of these in data-parsing code is defensive slicing with explicit length validation. Instead of `row[5]`, write `if len(row) <= 5 { return fmt.Errorf("row has %d columns, expected >= 6", len(row)) }` then `row[5]`. It's verbose but correct. Some Go teams enforce this with a custom linter rule that flags direct slice indexing without a preceding length check — there's a `go-staticcheck` rule SA1019 and custom `go/analysis` passes that can implement this.

The adjacent panic worth knowing: `slice bounds out of range [a:b]` when using sub-slice expressions like `s[2:1]` (where the low bound exceeds the high bound). This is different from the basic index-out-of-range and occurs most often when computing sub-slice indices from user inputs or offsets without validating that `low <= high <= len(s)`. The full set of conditions for a valid slice expression `s[low:high:max]` is `0 <= low <= high <= max <= cap(s)` — all four must hold simultaneously.

By Bikram Nath · Curator · Updated June 2026

Frequently asked questions

Does Go check bounds at compile time?

Only for constant indices on arrays with constant length — `var a [3]int; _ = a[3]` is a compile error. For slices and runtime-computed indices, bounds checking happens at runtime and causes a panic. The Go compiler emits bounds-check elimination (BCE) for indices it can prove are safe statically, but this is an optimization, not a safety guarantee.

What is the safe way to get the last element of a slice?

Always check length first: `if len(s) > 0 { last := s[len(s)-1] }`. Or use `s[len(s)-1]` only inside a block that already confirmed length. Some developers create a helper: `func last[T any](s []T) (T, bool) { if len(s) == 0 { var z T; return z, false }; return s[len(s)-1], true }`.

Can I use recover() to catch index-out-of-range panics?

Yes, `recover()` catches all panics including index-out-of-range. But using recover as a substitute for bounds checking is an anti-pattern — it's slower, harder to read, and hides bugs. Fix the bounds check instead. The legitimate use of recover is in a top-level HTTP handler to prevent a single bad request from crashing the server.

disclosure:Errordex runs AdSense, has zero third-party affiliate or sponsored links, and occasionally links to the editor’s own paid digital products (clearly labelled). Every fix is cross-referenced against the official sources listed in the “sources” sidebar before it ships. If a fix here didn’t work for you, please email so we can update the page.