Arrays
An array in Go is a fixed-size block of values, all of the same type. The size is baked into the type at compile time and never changes. If you need a resizable collection (and you almost always will), you reach for a slice, which is the subject of the next lesson. This lesson exists because slices are built on arrays, and the slice behaviour you meet shortly makes much more sense once you know what the foundation looks like.
Declaring an array
The size sits inside square brackets, before the element type:
var a [3]int
fmt.Println(a) // [0 0 0]
b := [3]int{10, 20, 30}
fmt.Println(b) // [10 20 30]
var a [3]int declares a three-element int array with no initial values; each position starts at the zero value (0 for int, "" for string, false for bool). [3]int{10, 20, 30} is an array literal that spells out all three elements.
You can also let the compiler count elements for you with ... in place of the length:
days := [...]string{"Mon", "Tue", "Wed", "Thu", "Fri"}
fmt.Println(len(days)) // 5
[...]string{...} means "make the array exactly as long as the literal". The final type is still a fixed-size [5]string; the compiler just saves you from counting.
The length is part of the type
This is the biggest reason arrays feel awkward in practice. [3]int and [4]int are two different types. The compiler treats them as unrelated, and refuses to mix them:
var x [3]int
var y [4]int
x = y // compile error: cannot use y (type [4]int) as type [3]int
A function that takes [3]int cannot accept [4]int, and vice versa. That rule makes arrays awkward once you want one function to work across many lengths. Slices fix this problem by separating the length of the data from the type of the container.
Arrays are value types
Assigning an array to another variable copies every element. Passing an array to a function does the same:
a := [3]int{1, 2, 3}
b := a
b[0] = 99
fmt.Println(a) // [1 2 3] (unchanged)
fmt.Println(b) // [99 2 3]
a and b are independent after b := a. Changing b does not touch a. If you are coming from Java, Python, or JavaScript, this is the opposite of what arrays usually do: there, arrays behave like references and changes leak across variables. In Go, arrays are plain values. If you want the reference-like behaviour, you use a slice.
The same rule applies to function calls. An array passed to a function is copied, and the function mutates its own local copy:
func zero(arr [3]int) {
arr[0] = 0
}
func main() {
a := [3]int{1, 2, 3}
zero(a)
fmt.Println(a) // [1 2 3] (untouched)
}
zero received a brand-new [3]int and wrote 0 into its first slot. The caller's a is unaffected because it was copied wholesale on the call. If you want shared mutation instead of copying, slices are the usual tool.
When to actually use arrays
In real Go code, slices cover almost every use case where other languages would reach for a list or array. Arrays themselves show up in a few specific places:
- When the size is fixed by the problem.
[16]bytefor an MD5 hash,[4]intfor the corners of a rectangle,[64]bytefor a cryptographic key. - As the underlying storage for a slice. Every slice points at an array; that is why the slice lessons still need this background.
- Code that wants fixed size and value semantics together.
For the other 95% of cases, slices are the right tool. Keep reading.
The starter declares scores := [4]int{70, 82, 90, 65}. Add code in main:
- Print the length of the array with
len(scores)on its own line. - Range over
scoresand print each index and value on its own line. - Reassign
scores[3]to100, then printscores[3]to confirm the update.
4 0 70 1 82 2 90 3 65 100