Arrays
Arrays in Go are value types with fixed sizes. Understanding their memory layout and copy semantics is essential before mastering slices β which are built on top of arrays.
π― Learning Objectives
- Declare and initialise arrays in all forms
- Understand array memory layout: contiguous, fixed-size blocks
- Explain value semantics: array assignment creates a full copy
- Compare arrays correctly (Go arrays are comparable!)
- Iterate arrays with index and value forms
- Work with multi-dimensional arrays
- Know when to use arrays vs slices
π Declaration & Initialisation
In Go, an array's length is part of its type. [3]int and [5]int are completely different types. You cannot assign one to the other.
package main
import "fmt"
func main() {
// Form 1: declare, zero-initialised
var a [5]int
fmt.Println(a) // [0 0 0 0 0]
// Form 2: array literal
b := [5]int{10, 20, 30, 40, 50}
fmt.Println(b) // [10 20 30 40 50]
// Form 3: partial initialisation (rest is zeroed)
c := [5]int{10, 20} // [10 20 0 0 0]
fmt.Println(c)
// Form 4: specific index initialisation
d := [5]int{1: 100, 3: 300} // [0 100 0 300 0]
fmt.Println(d)
// Form 5: ... let compiler count the length
e := [...]int{1, 2, 3, 4, 5, 6} // length inferred: 6
fmt.Println(len(e)) // 6
// Form 6: 2D array (matrix)
matrix := [3][4]int{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
}
fmt.Println(matrix[1][2]) // 7 (row 1, col 2)
// Length is part of the type β these are DIFFERENT types!
var x [3]int
var y [5]int
_ = x; _ = y
// x = y // β compile error: cannot use [5]int as [3]int!
}π§ Memory Layout
Arrays in Go are stored as a contiguous block of memory. All elements are adjacent β no pointers between them. This is why array access is O(1) and cache-friendly.
Random access is O(1): simply compute the address.
package main
import (
"fmt"
"unsafe"
)
func main() {
a := [5]int64{10, 20, 30, 40, 50}
// Total size of the array in bytes
fmt.Printf("Total size: %d bytes\n", unsafe.Sizeof(a)) // 40 bytes (5 Γ 8)
// Size of each element
fmt.Printf("Element size: %d bytes\n", unsafe.Sizeof(a[0])) // 8 bytes
// Memory addresses of elements (should be contiguous)
for i := range a {
fmt.Printf("a[%d] @ %p\n", i, &a[i])
}
// Addresses increase by 8 bytes each time
}π Value Semantics β Array Copying
This is the most important property of Go arrays: assignment copies the entire array. Arrays are value types, not reference types.
package main
import "fmt"
func modifyArray(arr [5]int) {
arr[0] = 999 // modifies the LOCAL COPY only!
fmt.Println("inside function:", arr)
}
func modifyArrayPtr(arr *[5]int) {
arr[0] = 999 // modifies the ORIGINAL via pointer
fmt.Println("inside function:", *arr)
}
func main() {
a := [5]int{1, 2, 3, 4, 5}
// Assignment copies the whole array
b := a // b is a FULL COPY of a
b[0] = 99
fmt.Println("a:", a) // [1 2 3 4 5] β unchanged
fmt.Println("b:", b) // [99 2 3 4 5] β independent
// Passing to function also copies
modifyArray(a)
fmt.Println("after modifyArray:", a) // [1 2 3 4 5] β unchanged!
// To modify: pass a pointer
modifyArrayPtr(&a)
fmt.Println("after modifyArrayPtr:", a) // [999 2 3 4 5] β changed!
// Cost: copying a large array is expensive
// [1_000_000]int β passing this to a function copies 8 MB!
// Always pass large arrays by pointer, or use slices
}Passing a large array (say [1000000]int) to a function copies 8 MB on a 64-bit system. This is a significant performance issue. Always pass large arrays by pointer (*[N]T) or, better yet, use a slice.
βοΈ Array Comparison
Unlike slices (which cannot be compared with ==), Go arrays can be compared with == if their element type is comparable. Two arrays are equal if all elements are equal.
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) // true β all elements equal
fmt.Println(a == c) // false β c[2]=4 != a[2]=3
fmt.Println(a != c) // true
// Arrays of different lengths are different TYPES β can't compare
// d := [4]int{1, 2, 3, 0}
// fmt.Println(a == d) // β compile error: [3]int != [4]int
// Arrays as map keys (since they are comparable)
type Point [2]int
visited := map[Point]bool{}
visited[Point{1, 2}] = true
visited[Point{3, 4}] = true
fmt.Println(visited[Point{1, 2}]) // true
fmt.Println(visited[Point{5, 6}]) // false
// But slices CANNOT be used as map keys:
// m := map[[]int]bool{} // β compile error!
}π Iteration
package main
import "fmt"
func main() {
a := [5]int{10, 20, 30, 40, 50}
// Form 1: index-based (C-style)
fmt.Println("index-based:")
for i := 0; i < len(a); i++ {
fmt.Printf(" a[%d] = %d\n", i, a[i])
}
// Form 2: range (idiomatic Go)
fmt.Println("range:")
for i, v := range a {
fmt.Printf(" [%d] = %d\n", i, v)
}
// Form 3: only values (discard index)
sum := 0
for _, v := range a {
sum += v
}
fmt.Println("sum:", sum) // 150
// Form 4: only indices (useful for in-place modification)
for i := range a {
a[i] *= 2
}
fmt.Println("doubled:", a) // [20 40 60 80 100]
// 2D array iteration
matrix := [3][3]int{{1,2,3},{4,5,6},{7,8,9}}
for i, row := range matrix {
for j, val := range row {
fmt.Printf("m[%d][%d]=%d ", i, j, val)
}
fmt.Println()
}
}π― Arrays vs Slices β When to Use Each
| Use Arrays When | Use Slices When |
|---|---|
| Size is known and fixed at compile time | Size is dynamic or unknown |
| You want value semantics (copy on assign) | You need to share data between functions without copying |
| Using as map keys (slices can't be keys) | Almost all collections in Go |
| Fixed-size buffers: SHA256 hash [32]byte, AES key [16]byte | Lists, queues, dynamic buffers |
| Small fixed-size data: 2D/3D coordinates [3]float64 | Anything you'd use a list/array for in other languages |
package main
import (
"crypto/sha256"
"fmt"
)
// SHA256 hash is naturally a [32]byte β fixed size, value semantics
func hashFile(data []byte) [32]byte {
return sha256.Sum256(data) // returns [32]byte (array, not slice!)
}
// Fixed-size 3D vector
type Vec3 [3]float64
func (v Vec3) Add(other Vec3) Vec3 {
return Vec3{v[0]+other[0], v[1]+other[1], v[2]+other[2]}
}
func (v Vec3) Dot(other Vec3) float64 {
return v[0]*other[0] + v[1]*other[1] + v[2]*other[2]
}
func main() {
h := hashFile([]byte("hello"))
fmt.Printf("SHA256: %x\n", h) // SHA256 of "hello"
pos := Vec3{1, 0, 0}
vel := Vec3{0, 1, 0}
newPos := pos.Add(vel)
fmt.Println("position:", newPos) // [1 1 0]
// Compare two hashes β array comparison works!
h2 := hashFile([]byte("hello"))
fmt.Println("hashes equal:", h == h2) // true
}π€ Interview Questions
Array: Fixed-size, value type. Length is part of the type ([3]int β [5]int). Assignment and function passing creates a full copy. Can be used as map keys. Comparable with ==.
Slice: Dynamic-size, reference type (header pointing to underlying array). Length and capacity can change. Assignment shares the underlying array. Cannot be map keys. Cannot compare with ==.
In practice, slices are used for ~95% of all collection needs in Go. Arrays are mainly used for fixed-size data (SHA256 hashes, AES keys, coordinates) where value semantics matter.
Memory: Both store elements contiguously. A slice header (pointer + len + cap) is 24 bytes on 64-bit systems.
Arrays: Yes. Two arrays are equal if they have the same type AND all corresponding elements are equal. [3]int{1,2,3} == [3]int{1,2,3} is true. This makes arrays usable as map keys.
Slices: No. Slices are not comparable with == (except comparing to nil). This is because slices are reference types β two slices could point to different underlying arrays but have the same values, or the same array with different lengths. To compare slice contents, use reflect.DeepEqual or compare element-by-element.
Production gotcha: Developers from Python/Java often try slice1 == slice2. This is a compile error in Go. Use slices.Equal(a, b) (Go 1.21+) or reflect.DeepEqual(a, b).
π§ͺ Quiz
[5]int to a [3]int variable?a := [3]int{1,2,3}; b := a; b[0] = 99, what is a[0]?[3]int{1,2,3} == [3]int{1,2,3} evaluate to?len([...]int{10, 20, 30, 40, 50})?π Cheat Sheet
var a [5]int // [0 0 0 0 0][5]int{1, 2, 3, 4, 5}[...]int{1, 2, 3}[5]int{1: 10, 3: 30}len(a)Value type β assignment copies![3]int{1,2,3} == [3]int{1,2,3} // truefor i, v := range a { }unsafe.Sizeof(a) == n*elemSizefunc f(a *[5]int) { }