Arrays
Understand Go's fixed-size arrays: how to declare and index them, and why arrays are value types that get copied on assignment.
Introduction
An array in Go is a fixed-size, ordered collection of elements that all share the same type. Once you declare an array with a given length, that length is baked into the array's type and can never change. Arrays are the foundation that Go's much more commonly used slices are built on top of, so understanding them well makes slices click much faster.
- How to declare arrays with a fixed size.
- How to index into and measure the length of an array.
- How to loop over an array's elements.
- Why Go arrays are value types, copied on assignment and function calls.
- How to declare multi-dimensional arrays.
Declaring Arrays
An array type is written as [n]T, where n is the fixed number of elements and T is the element type. The size is part of the type itself — [3]int and [5]int are different, incompatible types.
package main
import "fmt"
func main() { // Zero-valued array: all elements start at 0. var scores [3]int fmt.Println(scores)
// Array literal with explicit values. names := [3]string{"Alice", "Bob", "Charlie"} fmt.Println(names)
// Let the compiler count the elements with "...". primes := [...]int{2, 3, 5, 7, 11} fmt.Println(primes, "length:", len(primes))}Click Run to see what this code prints.
Indexing and Length
Array elements are accessed with zero-based indexing, just like slices. The built-in len function returns the number of elements. Accessing an index outside the valid range causes a runtime panic, so bounds matter.
package main
import "fmt"
func main() { temps := [5]float64{72.5, 75.0, 68.3, 70.1, 74.8}
fmt.Println("First reading:", temps[0]) fmt.Println("Last reading:", temps[len(temps)-1])
temps[2] = 69.9 // update an element fmt.Println("Updated:", temps)}Click Run to see what this code prints.
Iterating Over Arrays
The range keyword works on arrays just as it does on slices, giving you both the index and the value on each iteration.
package main
import "fmt"
func main() { grades := [4]int{88, 92, 75, 100}
total := 0 for i, grade := range grades { fmt.Printf("Student %d scored %d\n", i+1, grade) total += grade }
average := float64(total) / float64(len(grades)) fmt.Printf("Average: %.2f\n", average)}Click Run to see what this code prints.
Arrays Are Value Types
This is the single most important fact about Go arrays: they are value types, not reference types. When you assign an array to a new variable, or pass it to a function, Go copies the entire array. Modifying the copy never affects the original. This is a common source of confusion for developers coming from languages where arrays are always references.
package main
import "fmt"
func tryToModify(arr [3]int) { arr[0] = 999 // modifies the local copy only}
func main() { original := [3]int{1, 2, 3} copied := original // full copy happens here
copied[0] = 100
fmt.Println("original:", original) fmt.Println("copied: ", copied)
tryToModify(original) fmt.Println("after function call:", original)}Click Run to see what this code prints.
Because copying happens on assignment and on every function call, arrays can be expensive for large sizes. This value-type behavior — plus the inconvenience of a fixed length — is exactly why Go code almost always reaches for slices instead, which you'll learn about next.
Multi-Dimensional Arrays
Go supports arrays of arrays, giving you multi-dimensional grids such as matrices or game boards.
package main
import "fmt"
func main() { var board [3][3]string
for i := range board { for j := range board[i] { board[i][j] = "-" } } board[1][1] = "X"
for _, row := range board { fmt.Println(row) }}Click Run to see what this code prints.
Common Mistakes
- Expecting a function parameter of array type to mutate the caller's array — it receives a copy, not a reference.
- Trying to append to an array with append — that function only works on slices.
- Assuming [5]int and [10]int are interchangeable — the length is part of the type, so they cannot be assigned to each other.
- Indexing out of bounds with a variable index that wasn't validated, causing a runtime panic.
- Using large arrays as function parameters without realizing the copy cost on every call.
Best Practices
- Reach for arrays only when the size is truly fixed and known at compile time, such as a day-of-week lookup table.
- Use slices for anything that might grow or whose size isn't known upfront — which is most real-world data.
- Pass a pointer to an array (or use a slice) if you need a function to mutate it in place.
- Use the [...]T{...} form when you want the compiler to count elements for you, reducing off-by-one errors.
- Prefer range over manual index loops for readability, unless you need to skip or jump indices.
Frequently Asked Questions
Rarely in application code. Arrays make sense for small, fixed-size data like a 3x3 board or a lookup table where the size will never change. Everywhere else, use a slice.
Yes, if they have the same length and element type. Go compares them element by element, which is not possible with slices.
Yes, len() returns the number of elements for both. The difference is that an array's length is fixed and part of its type, while a slice's length can change at runtime.
Key Takeaways
- An array type [n]T has a fixed length n baked into the type itself.
- Arrays are value types — assignment and function calls copy the entire array.
- Modifying a copy or a function parameter never affects the original array.
- Multi-dimensional arrays are arrays of arrays, indexed with board[i][j].
- Because of their fixed size and copy-on-assignment behavior, slices are used far more often than arrays in idiomatic Go.
Summary
Arrays give Go a fixed-size, value-type collection that's predictable and cheap for small, known sizes — but their inflexibility is exactly what slices were designed to fix. Next, you'll learn about slices, the dynamic, flexible collection type that Go developers reach for in almost every situation.