πŸ“Š Chapter 6 of 20

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.

⏱ β€”πŸ“Š Beginner

🎯 Learning Objectives

βœ… After this chapter you will:
  • 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

πŸ“Œ Key Property

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.

go
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!
}
βœ… Output
[0 0 0 0 0] [10 20 30 40 50] [10 20 0 0 0] [0 100 0 300 0] 6 7

🧠 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.

πŸ“Š [5]int32 Memory Layout (5 Γ— 4 bytes = 20 bytes total)
var a [5]int32 = [5]int32{10, 20, 30, 40, 50}
base+010 base+420 base+830 base+1240 base+1650
a[i] is at address: base + i Γ— sizeof(int32)
Random access is O(1): simply compute the address.
go
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
}
βœ… Example Output
Total size: 40 bytes Element size: 8 bytes a[0] @ 0xc0000b4000 a[1] @ 0xc0000b4008 a[2] @ 0xc0000b4010 a[3] @ 0xc0000b4018 a[4] @ 0xc0000b4020

πŸ“‹ 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.

go
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
}
βœ… Output
a: [1 2 3 4 5] b: [99 2 3 4 5] inside function: [999 2 3 4 5] after modifyArray: [1 2 3 4 5] inside function: [999 2 3 4 5] after modifyArrayPtr: [999 2 3 4 5]
⚠️ Large Array Performance

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.

go
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!
}
βœ… Output
true false true true false

πŸ”„ Iteration

go
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()
    }
}
βœ… Output
index-based: a[0] = 10 a[1] = 20 a[2] = 30 a[3] = 40 a[4] = 50 range: [0] = 10 [1] = 20 [2] = 30 [3] = 40 [4] = 50 sum: 150 doubled: [20 40 60 80 100] m[0][0]=1 m[0][1]=2 m[0][2]=3 m[1][0]=4 m[1][1]=5 m[1][2]=6 m[2][0]=7 m[2][1]=8 m[2][2]=9

🎯 Arrays vs Slices β€” When to Use Each

Use Arrays WhenUse Slices When
Size is known and fixed at compile timeSize 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]byteLists, queues, dynamic buffers
Small fixed-size data: 2D/3D coordinates [3]float64Anything you'd use a list/array for in other languages
go
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

1
What is the difference between an array and a slice in Go?
β–Ά
Detailed Answer

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.

2
Can Go arrays be compared with ==? Can slices?
β–Ά
Detailed Answer

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

🧩
Arrays
4 questions
1. Can you assign a [5]int to a [3]int variable?
2. After a := [3]int{1,2,3}; b := a; b[0] = 99, what is a[0]?
3. What does [3]int{1,2,3} == [3]int{1,2,3} evaluate to?
4. What is len([...]int{10, 20, 30, 40, 50})?
β€”

πŸ“Œ Cheat Sheet

πŸ“Œ Arrays β€” Quick Reference
Zero value
var a [5]int // [0 0 0 0 0]
Literal
[5]int{1, 2, 3, 4, 5}
Inferred len
[...]int{1, 2, 3}
Indexed init
[5]int{1: 10, 3: 30}
Length
len(a)
Semantics
Value type β€” assignment copies!
Comparable
[3]int{1,2,3} == [3]int{1,2,3} // true
Iterate
for i, v := range a { }
Size
unsafe.Sizeof(a) == n*elemSize
Pass by ref
func f(a *[5]int) { }