Learn Go
beginner1 min read

Arrays and Slices

Go provides two sequence types: arrays (fixed-length) and slices (dynamic views over arrays). In practice, you will use slices almost exclusively — arrays are mainly useful as the backing store for slices.

Arrays

An array has a fixed size that is part of its type. [3]int and [4]int are different types:

package main
 
import "fmt"
 
func main() {
    var nums [3]int        // zero-valued array: [0 0 0]
    nums[0] = 10
    nums[1] = 20
    nums[2] = 30
 
    primes := [5]int{2, 3, 5, 7, 11}
 
    fmt.Println(nums)   // [10 20 30]
    fmt.Println(primes) // [2 3 5 7 11]
    fmt.Println(len(primes)) // 5
}

Use [...] to let the compiler count the elements:

package main
 
import "fmt"
 
func main() {
    days := [...]string{"Mon", "Tue", "Wed", "Thu", "Fri"}
    fmt.Println(len(days)) // 5
}

Slices

A slice is a lightweight descriptor that references a contiguous section of an array. It has a length and a capacity:

package main
 
import "fmt"
 
func main() {
    s := []int{10, 20, 30, 40, 50}
 
    fmt.Println(s)        // [10 20 30 40 50]
    fmt.Println(len(s))   // 5
    fmt.Println(cap(s))   // 5
 
    // Slicing: s[low:high] — includes low, excludes high
    fmt.Println(s[1:4])   // [20 30 40]
    fmt.Println(s[:3])    // [10 20 30]
    fmt.Println(s[2:])    // [30 40 50]
}

Idiomatic Go: Declare slices with a literal ([]T{...}) or make. Avoid using arrays directly unless you need their value-copy semantics.

make and append

make([]T, length, capacity) allocates a slice with a backing array. append grows a slice, allocating a new backing array if needed:

package main
 
import "fmt"
 
func main() {
    s := make([]int, 0, 4) // len=0, cap=4
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
 
    for i := 1; i <= 6; i++ {
        s = append(s, i*10)
        fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
    }
}

When append needs more space it typically doubles the capacity, so amortised appending is O(1).

Slices Share Underlying Arrays

Slicing does not copy data. Two slices can share the same backing array:

package main
 
import "fmt"
 
func main() {
    original := []int{1, 2, 3, 4, 5}
    view     := original[1:4]
 
    view[0] = 99 // modifies original[1]
 
    fmt.Println(original) // [1 99 3 4 5]
    fmt.Println(view)     // [99 3 4]
}

Use the three-index slice s[low:high:max] to limit the capacity and prevent unintended sharing.

Iterating with range

range over a slice returns the index and a copy of the element:

package main
 
import "fmt"
 
func main() {
    fruits := []string{"apple", "banana", "cherry"}
 
    for i, f := range fruits {
        fmt.Printf("[%d] %s\n", i, f)
    }
 
    // Sum all elements
    nums := []int{1, 2, 3, 4, 5}
    sum := 0
    for _, n := range nums {
        sum += n
    }
    fmt.Println("sum:", sum) // sum: 15
}

Copying Slices

Use the built-in copy to copy elements between slices:

package main
 
import "fmt"
 
func main() {
    src := []int{1, 2, 3}
    dst := make([]int, len(src))
    copy(dst, src)
    dst[0] = 99
 
    fmt.Println(src) // [1 2 3] — unchanged
    fmt.Println(dst) // [99 2 3]
}

Key Takeaways