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{...}) ormake. 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
- Arrays are fixed-size and value types; slices are dynamic views over arrays.
len(s)returns the number of elements;cap(s)returns the available space before reallocation.appendalways returns a potentially new slice — always assign the result back.- Slices share underlying arrays; use
copyto get an independent clone. rangeis the idiomatic way to iterate over a slice.