17. Arrays: fixed-size, and rarely what you want

📖 Reading · 8 min
💡 Every code box below is live — edit it and hit Run.

Go has two list-like types, and beginners mix them up constantly. This lesson is the short one: arrays, which have a fixed size baked into their type. The next lesson is about slices, which is what you'll actually use 95% of the time. Understanding arrays first makes slices make sense.

Declaring an array

The length is part of the declaration — and part of the type:

package main

import "fmt"

func main() {
    var scores [3]int
    fmt.Println(scores)

    scores[0] = 90
    scores[1] = 85
    scores[2] = 78
    fmt.Println(scores)
    fmt.Println("first:", scores[0], "length:", len(scores))
}

var scores [3]int gives you three ints, all at their zero value. There's no "empty" array in Go — an array of 3 always has 3 elements, they just start at zero.

Index from 0, use len() for the length. Reading past the end is a runtime panic, not a silent nil — try changing scores[0] to scores[5] and running it.

Literals

package main

import "fmt"

func main() {
    primes := [5]int{2, 3, 5, 7, 11}
    fmt.Println(primes, len(primes))

    days := [...]string{"Mon", "Tue", "Wed"}
    fmt.Println(days, len(days))

    sparse := [5]int{0: 1, 4: 9}
    fmt.Println(sparse)
}

[...] tells the compiler to count the elements for you — it's still a fixed size, you just didn't have to type it. The third form sets specific indexes and leaves the rest at zero.

The length is part of the type

This is the thing to remember:

package main

import "fmt"

func main() {
    var a [3]int
    var b [4]int

    fmt.Printf("%T and %T\n", a, b)
    // a = b  // won't compile: different types
}

[3]int and [4]int are different types. A function that takes a [3]int cannot be called with a [4]int. That rigidity is exactly why arrays are impractical for everyday code — you almost never know your data's length when you write the function.

Arrays are values — they copy

Assign an array and you get a full copy, not a reference:

package main

import "fmt"

func main() {
    original := [3]int{1, 2, 3}
    copied := original

    copied[0] = 999

    fmt.Println("original:", original)
    fmt.Println("copied:  ", copied)
}

original is untouched. The same applies when you pass an array to a function — it gets its own copy, so a big array means a big copy.

This is the opposite of what you'd expect coming from Python lists or JavaScript arrays, and the opposite of how Go slices behave. Hold onto that contrast; it's the whole point of the next two lessons.

Comparing and ranging

Arrays of comparable elements can be compared directly with ==:

package main

import "fmt"

func main() {
    a := [3]int{1, 2, 3}
    b := [3]int{1, 2, 3}
    c := [3]int{1, 2, 4}

    fmt.Println(a == b, a == c)

    for i, v := range a {
        fmt.Printf("index %d holds %d\n", i, v)
    }
}

== compares element by element. (Slices, you'll find, can't be compared this way at all.)

When arrays are actually right

You will meet arrays in a few honest places:

  • Fixed-size buffers and cryptographic digests — [32]byte for a SHA-256 hash is exactly right, and its fixed size is a feature.
  • As the backing store for slices — every slice points at an array.
  • Where copying semantics are what you want and the size is genuinely known.

Everywhere else: use a slice.

Your turn

Create an array of 4 ints holding 10, 20, 30, 40, then loop over it and print the running total after each element, one per line:

10
30
60
100
package main

import "fmt"

func main() {
    nums := [4]int{10, 20, 30, 40}
    // print the running total after each element
}
package main

import "fmt"

func main() {
    nums := [4]int{10, 20, 30, 40}
    total := 0
    for _, n := range nums {
        total += n
        fmt.Println(total)
    }
}

Now for the type you'll actually use every day: slices.