Go Arrays Link to heading
In Go, an array is a fixed-length, ordered collection of values of the same type. The important phrase is fixed-length: once an array has been declared, its length cannot change.
That one detail explains why arrays are less common in day-to-day Go code than slices. Slices are flexible and can grow; arrays are exact. Still, arrays are worth understanding because slices are built on top of them, and because arrays make Go’s value semantics very clear.
The length is part of the type Link to heading
In Go, the length of an array is part of its type. That means [5]int and [10]int are different types, even though both contain int values.
var ints [5]int
fmt.Printf("%T\n", ints) // [5]int is the type
The length must be a non-negative constant that can be represented as an int.
var five [5]int
var ten [10]int
fmt.Printf("%T\n", five) // [5]int
fmt.Printf("%T\n", ten) // [10]int
Because those are different types, you cannot assign one to the other:
five = ten // compile error
Accessing elements Link to heading
The values stored in an array are called elements. You access them by index, and Go indexes arrays from zero.
numbers := [5]int{10, 20, 30, 40, 50}
fmt.Println(numbers[0]) // 10
fmt.Println(numbers[4]) // 50
For a 5-element array, the valid indexes are 0 through 4. Trying to access numbers[5] is out of range because it asks for a sixth element.
Arrays allow many values to be stored in a single data structure while providing very fast access. This is possible because:
- All values in an array are of the same type and have the same size.
- Elements are stored in contiguous memory locations.
Arrays are values Link to heading
Arrays in Go are values. An array variable represents the entire array, not a pointer to the first element. When you pass an array to a function, Go passes a copy.
func changeFirst(numbers [5]int) {
numbers[0] = 99
}
var numbers [5]int
changeFirst(numbers)
fmt.Println(numbers[0]) // 0
If you want a function to modify the original array, pass a pointer:
func changeFirst(numbers *[5]int) {
numbers[0] = 99
}
var numbers [5]int
changeFirst(&numbers)
fmt.Println(numbers[0]) // 99
In most application code, you will usually pass a slice instead. But the example is useful because it shows that arrays follow Go’s normal value-copying rules.
Declaring arrays Link to heading
You can declare an array with var:
var ints [5]int
That creates an array of five integers, all initialized to the zero value for int, which is 0.
You can also use a composite literal:
ints := [5]int{1, 2, 3, 4, 5}
Or let the compiler infer the length from the values you provide:
ints := [...]int{1, 2, 3, 4, 5} // [5]int
The ... does not make the array dynamic. It only asks the compiler to count the elements for you.
Initializing arrays Link to heading
Go does not leave declared variables uninitialized. If you declare an array without providing values, every element gets the zero value of the element type.
var ints [5]int
fmt.Println(ints) // [0 0 0 0 0]
var names [3]string
fmt.Println(names) // [ ]
You can provide all values:
ints := [5]int{1, 2, 3, 4, 5}
Or only some of them:
ints := [5]int{1, 2}
fmt.Println(ints) // [1 2 0 0 0]
Looping over arrays Link to heading
Use a regular for loop when you need index control:
numbers := [5]int{1, 2, 3, 4, 5}
for i := 0; i < len(numbers); i++ {
fmt.Println(numbers[i])
}
Use range when you want the index and value:
numbers := [5]int{1, 2, 3, 4, 5}
for i, v := range numbers {
fmt.Printf("numbers[%d] = %d\n", i, v)
}
If you do not need the index, use _ to ignore it:
for _, v := range numbers {
fmt.Println(v)
}
How arrays are stored in memory Link to heading
An array’s elements are stored next to each other in memory. If an array has N elements, and each element takes M bytes, the array needs N * M bytes of contiguous storage.
Formula:
address = base + (index * element_size)
Where:
baseis the address of the first element.indexis the element’s position in the array.element_sizeis the size of one element in bytes.
Here is a small example using unsafe to print the addresses. You do not need unsafe for normal array usage; this is only to make the layout visible.
package main
import (
"fmt"
"unsafe"
)
func main() {
ints := [5]int{1, 4, 5, 8, 9}
fmt.Printf("Address of the array: %p\n", &ints)
fmt.Printf("Index\tValue\tAddress\n")
for i := 0; i < len(ints); i++ {
ptr := unsafe.Pointer(&ints[i])
fmt.Printf("%d\t%d\t%p\n", i, ints[i], ptr)
}
}
Sample Output:
Address of the array: 0xc0000b4060
Index Value Address
0 1 0xc0000b4060
1 4 0xc0000b4068
2 5 0xc0000b4070
3 8 0xc0000b4078
4 9 0xc0000b4080
On this machine, int occupies 8 bytes, so each address is 8 bytes after the previous one. On a different architecture, int may have a different size.
To find the address of ints[4] in the sample output:
- base_address = 0xc0000b4060
- offset = 4 * 8 = 32 = 0x20
- address = 0xc0000b4060 + 0x20 = 0xc0000b4080
Arrays vs slices Link to heading
Use arrays when the exact length is part of the meaning of the value. For example, a SHA-256 checksum is naturally a [32]byte.
Use slices when you need a collection that can grow, shrink, or be passed around without copying the whole underlying array.
That is the practical rule: arrays give you fixed-size values; slices give you flexible views over arrays.