Arrays
Understand Go arrays — fixed-size sequences, declaration syntax, value semantics, and working with multi-dimensional arrays.
An array in Go is a fixed-length sequence of elements that all share a single type. You decide the number of elements when you declare the array, and that number stays constant for the lifetime of the array. Arrays are the foundation that slices build on, so getting comfortable with arrays first makes slices far easier to learn.
Array Declaration and Type
An array type is written [n]T, where n is the number of elements and T is the type of each element. The length n is part of the type — [3]int and [5]int are completely different types. This is the single most important thing to remember about arrays in Go, because it means you can never resize an array or assign one array type to another that has a different length.
var scores [3]int
fmt.Println(scores)
When you declare an array without initializing it, every element is automatically set to the zero value of its type. The program above prints [0 0 0] because int's zero value is 0.
You access individual elements with square brackets and a zero‑based index. The first element is at index 0, the last at index n - 1.
var temps [3]float64
temps[0] = 23.5
temps[1] = 19.0
temps[2] = 27.1
fmt.Println(temps) // [23.5 19 27.1]
A loop is the natural way to work through an array. The range keyword gives you both the index and the value at each position.
cities := [3]string{"Berlin", "Tokyo", "Nairobi"}
for i, city := range cities {
fmt.Printf("City %d: %s\n", i, city)
}
Zero Value Initialization:
All elements of a newly declared array are immediately usable and set to the zero value of the element type. For numeric types this is 0, for strings it's "", and for booleans it's false.
Size Is Part of the Type:
[3]int and [5]int are distinct, incompatible types. Assigning a [3]int to a variable that expects a [5]int is a compile‑time error, not a runtime conversion.
Array Literals
Array literals let you declare and fill an array in one step without writing out each index assignment.
fib := [5]int{0, 1, 1, 2, 3}
fmt.Println(fib) // [0 1 1 2 3]
You can supply fewer values than the array length. The remaining elements automatically get the zero value of the type.
partial := [4]string{"alpha"}
fmt.Println(partial) // [alpha ]
This prints [alpha ] — the three trailing positions are empty strings.
If you don’t want to count the elements yourself, use ... instead of a number. The compiler counts the values in the literal and sets the length for you. The resulting type is still fixed; you are just telling the compiler to do the counting.
colors := [...]string{"red", "green", "blue"}
fmt.Printf("Type: %T, Length: %d\n", colors, len(colors))
Fixed Size, Even with `...`:
The ... syntax only saves you from manually counting elements. It does not create a dynamically sized collection. Once the compiler infers the length, the array size is locked. For truly dynamic growth you need slices.
When you use ..., the compiler counts the literal values exactly. Giving zero values explicitly or leaving gaps is not a shortcut; the count always matches the number of values you write inside the braces.
Arrays as Values
Go arrays are value types — the variable holds the whole array, not a pointer to its first element. Assigning an array to another variable copies every element. Passing an array to a function also copies every element. Changes you make to the copy do not affect the original.
original := [3]string{"US", "UK", "JP"}
copyArr := original
copyArr[0] = "Canada"
fmt.Println("original:", original) // [US UK JP]
fmt.Println("copy: ", copyArr) // [Canada UK JP]
This behavior is different from languages where arrays are reference types. In Go, the copy is a completely independent block of memory.
Functions receive a copy of the array, so a function that modifies an array parameter leaves the caller’s data untouched.
func reset(arr [3]int) {
arr[0] = 0
fmt.Println("inside:", arr)
}
nums := [3]int{99, 88, 77}
reset(nums)
fmt.Println("outside:", nums) // [99 88 77]
Copy Cost for Large Arrays:
Because an array is copied in its entirety, passing a very large array by value can be expensive. If you have thousands of elements and need to avoid the copy, pass a pointer to the array or, in most Go programs, use a slice instead.
Predictable and Safe:
The value‑copy behavior means you never accidentally mutate data across function boundaries. If your program’s output matches expectations after modifying a copy, you can be confident the original is intact.
Two arrays of the same type can be compared with == and !=. The comparison checks every element.
a := [3]int{1, 2, 3}
b := [3]int{1, 2, 3}
fmt.Println(a == b) // true
Multi‑Dimensional Arrays
Go supports arrays with more than one dimension. The syntax nests array types: [n][m]T is an array of length n where each element is itself an array of length m of type T.
var grid [2][3]int
grid[0][0] = 1
grid[0][1] = 2
grid[0][2] = 3
grid[1][0] = 4
grid[1][1] = 5
grid[1][2] = 6
fmt.Println(grid) // [[1 2 3] [4 5 6]]
You can initialize a multi‑dimensional array with a literal. The inner braces group the rows, and every row must have the same number of columns.
board := [3][3]string{
{"X", "O", "X"},
{"O", "X", " "},
{" ", "O", "X"},
}
Trailing Comma in Multi‑line Literals:
When you spread a literal across multiple lines, every line that ends a group of values must have a trailing comma — including the last row. This is a requirement of Go’s automatic semicolon insertion rules. If you omit the comma, the compiler will complain.
Iterating over a multi‑dimensional array usually means nesting range loops. The outer loop walks through the rows, and the inner loop through the columns within each row.
for i, row := range board {
for j, cell := range row {
fmt.Printf("board[%d][%d] = %q\n", i, j, cell)
}
}
The length of the whole array is the first dimension, but you can also use len() on any row to get the column count. For a rectangular array, len(arr[0]) gives the length of the second dimension.
Multi‑dimensional arrays share the same value semantics as one‑dimensional arrays. Copying a multi‑dimensional array copies every element across all dimensions. This can be convenient for small matrices, but for performance‑sensitive code involving large grids, slices of slices or a single flat slice with manual index arithmetic may be more appropriate.
An array in Go is deliberately simple: it holds a fixed number of elements, its size is part of its type, and copying it means copying every element. These constraints are not accidental — they make array behavior predictable and safe, which is why slices are built on top of them rather than replacing them entirely. When you need a collection that stays the same size and you want guaranteed isolation between copies, arrays are the right tool.