arrays

arrays

fixed size, value type, rarely used directly in real go code. understand them because slices are built on top of them.

arrays in go are fixed size — the size is part of the type itself. [3]int and [4]int are completely different types, not interchangeable. in real go code you'll use slices almost always, but arrays are the foundation slices are built on.

declaring arrays

copy
// explicit size with values
arr1 := [5]int{200, 201, 400, 404, 500}
 
// let go count the size
arr2 := [...]int{200, 201, 400, 404, 500} // size inferred as 5
 
// declare with zero values, assign individually
var arr3 [5]int
arr3[0] = 200
arr3[1] = 201
// arr3 is now [200 201 0 0 0]

zero values depend on element type — int to 0, string to "", bool to false.

size is part of the type

copy
a := [3]int{1, 2, 3}
b := [4]int{1, 2, 3, 4}
 
fmt.Println(a == b) // compile error — mismatched types

this is why functions that take arrays only work for one specific size. real go code uses slices instead — slice functions work for any size.

copy
// only works for exactly [4]string
func contains(routes [4]string, target string) bool { ... }
 
// works for any size
func contains(routes []string, target string) bool { ... }

out of bounds — two kinds of errors

copy
arr := [5]int{1, 2, 3, 4, 5}
 
// compile time error — compiler knows the index is too large
arr[5] = 99 // index 5 out of bounds [5]
 
// runtime panic — dynamic index, compiler can't check
i := 5
arr[i] = 99 // compiles fine, panics when it runs

compile time = caught before the program runs. runtime = program crashes in production. always guard dynamic indices:

copy
if i < len(arr) {
    arr[i] = val
}

arrays are value types — not references

copy
// js — same reference, mutating copy mutates original
const copy = original
copy[0] = "changed" // original[0] is now "changed" too
 
// go — full copy, independent
original := [3]string{"admin", "user", "guest"}
copied   := original
copied[0] = "superadmin"
 
fmt.Println(original) // [admin user guest] — unchanged
fmt.Println(copied)   // [superadmin user guest]

arrays live on the stack — fixed size, short-lived. slices and maps live on the heap.

iterating

copy
routes := [4]string{"/health", "/users", "/posts", "/auth"}
 
// classic for
for i := 0; i < len(routes); i++ {
    fmt.Println(routes[i])
}
 
// range — cleaner
for i, v := range routes {
    fmt.Printf("%d: %s\n", i, v)
}

2d arrays

copy
roles       := [3]string{"admin", "moderator", "guest"}
permissions := [3]string{"read", "write", "delete"}
 
mat := [3][3]bool{
    {true, true, true},   // admin
    {true, true, false},  // moderator
    {true, false, false}, // guest — trailing comma required
}
 
for i := 0; i < len(mat); i++ {
    fmt.Printf("%s: ", roles[i])
    for j := 0; j < len(mat[i]); j++ {
        fmt.Printf("%s=%t ", permissions[j], mat[i][j])
        // use permissions[j] not [i] — j changes per column not row
    }
    fmt.Println()
}

gotchas

copy
// trailing comma required when closing brace is on its own line
mat := [3][3]bool{
    {true, true, true},
    {true, true, false},
    {true, false, false}, // remove this comma — compile error
}
 
// don't shadow built-in functions with variable names
copy := original // copy is a built-in in go — valid but shadows it
copied := original // use a different name
 
// runtime panics are silent until they happen
i := 10
arr[i] = 99 // compiles fine, crashes in production
// guard: if i < len(arr) { arr[i] = val }

when to actually use arrays — when size is a hard constraint defined by the problem. rgb [3]int, crypto keys where size is protocol-defined. anything where the size is a decision that can change → use a slice.