Arrays and Slices
Overview
Arrays and slices are Go’s primary sequence types. Arrays are fixed-size, while slices are dynamic views into arrays.
Arrays
Declaration
var arr [5]int // Zero values: [0 0 0 0 0]
arr := [5]int{1, 2, 3, 4, 5} // Literal
arr := [...]int{1, 2, 3} // Size inferred: [3]intProperties
- Fixed size (part of type):
[5]int≠[6]int - Value semantics: copying creates independent copy
- Zero value: array of zero values
a := [3]int{1, 2, 3}
b := a // Copy!
b[0] = 99
fmt.Println(a) // [1 2 3] (unchanged)Slices
Creation
var s []int // nil slice
s := []int{1, 2, 3} // Literal
s := make([]int, 5) // Length 5, capacity 5
s := make([]int, 0, 10) // Length 0, capacity 10From Array
arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4] // [2 3 4] - shares memory with arrSlice Internals & Reallocation
A slice header consists of three words: a pointer to the backing array, a length (len), and a capacity (cap).
flowchart TD
subgraph SliceHeader ["Slice Header (24 bytes on 64-bit)"]
PTR["ptr: 0x7FFF00"]
LEN["len: 3"]
CAP["cap: 4"]
end
subgraph BackingArray ["Underlying Array in Memory"]
E0["[0]: 10"]
E1["[1]: 20"]
E2["[2]: 30"]
E3["[3]: 0 (Unused cap)"]
end
PTR --> E0
When append() exceeds cap, Go allocates a new, larger backing array and copies existing elements:
flowchart LR
subgraph OldSlice ["Old Slice (cap=4)"]
O1["Array: [10, 20, 30, 40]"]
end
subgraph Realloc ["append(s, 50) triggers re-allocation"]
NewArr["New Array Allocated (cap=8)"]
Copy["Copy [10, 20, 30, 40] + Append 50"]
end
OldSlice -->|cap full| Realloc
Length and Capacity
s := make([]int, 3, 5)
len(s) // 3 (elements accessible)
cap(s) // 5 (space available)Slice Operations
Append
s := []int{1, 2, 3}
s = append(s, 4) // [1 2 3 4]
s = append(s, 5, 6, 7) // [1 2 3 4 5 6 7]
s = append(s, other...) // Append another sliceSlicing
s := []int{0, 1, 2, 3, 4, 5}
s[1:4] // [1 2 3]
s[:3] // [0 1 2]
s[3:] // [3 4 5]
s[:] // [0 1 2 3 4 5] (copy of slice header)Copy
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)The slices Package (Go 1.21+)
The standard library slices package provides generic utilities for common operations.
import "slices"
slices.Sort(nums) // Sort in place
slices.Reverse(nums) // Reverse in place
slices.Contains(nums, 42) // Search
slices.Index(nums, 42) // Find index
slices.Delete(nums, i, j) // Remove range [i, j)
slices.Clone(nums) // Shallow copy
slices.Equal(s1, s2) // CompareModern Loop Pattern (Go 1.23+)
Using iterators with slices:
for i, v := range slices.All(nums) { /* ... */ }
for v := range slices.Values(nums) { /* ... */ }nil vs Empty Slice
var nilSlice []int // nil
emptySlice := []int{} // not nil, but empty
nilSlice == nil // true
emptySlice == nil // false
len(nilSlice) // 0
len(emptySlice) // 0Common Patterns
Remove Element
s = append(s[:i], s[i+1:]...) // Remove element at index iInsert Element
s = append(s[:i], append([]int{v}, s[i:]...)...)Stack
stack = append(stack, v) // Push
v, stack = stack[len(stack)-1], stack[:len(stack)-1] // PopSummary
| Feature | Array | Slice |
|---|---|---|
| Size | Fixed | Dynamic |
| Type | [n]T |
[]T |
| Zero value | Array of zeros | nil |
| Comparison | == works |
Not comparable |
More examples
Example: array vs slice
Save as main.go and go run . (with go mod init example if needed).
package main
import "fmt"
func main() {
var arr [3]int = [3]int{1, 2, 3}
s := arr[:]
s[0] = 9
fmt.Println("arr:", arr)
fmt.Println("slice:", s, "len", len(s), "cap", cap(s))
}Expected:
arr: [9 2 3]
slice: [9 2 3] len 3 cap 3
Example: append and capacity growth
Save as main.go and go run . (with go mod init example if needed).
package main
import "fmt"
func main() {
s := make([]int, 0, 2)
fmt.Printf("start len=%d cap=%d\n", len(s), cap(s))
s = append(s, 1, 2)
fmt.Printf("full len=%d cap=%d %v\n", len(s), cap(s), s)
s = append(s, 3)
fmt.Printf("grow len=%d cap=%d %v\n", len(s), cap(s), s)
}Expected:
start len=0 cap=2
full len=2 cap=2 [1 2]
grow len=3 cap=4 [1 2 3]
Runnable example
Save as main.go. From an empty directory:
go mod init example
go run .package main
import (
"fmt"
"slices"
)
func main() {
// Arrays: fixed size, value semantics
a := [3]int{1, 2, 3}
b := a
b[0] = 99
fmt.Println("array a (unchanged):", a)
fmt.Println("array copy b:", b)
// Slices: dynamic, share backing array until reallocation
s := []int{10, 20, 30}
fmt.Printf("s=%v len=%d cap=%d\n", s, len(s), cap(s))
t := s[1:]
t[0] = 200
fmt.Println("after t[0]=200, s=", s, "t=", t)
// append may reallocate when cap is exceeded
s2 := make([]int, 3, 4)
copy(s2, []int{1, 2, 3})
fmt.Printf("before grow: %v len=%d cap=%d\n", s2, len(s2), cap(s2))
s2 = append(s2, 4)
fmt.Printf("cap full: %v len=%d cap=%d\n", s2, len(s2), cap(s2))
s2 = append(s2, 5)
fmt.Printf("after grow: %v len=%d cap=%d\n", s2, len(s2), cap(s2))
// Independent copy
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)
dst[0] = 7
fmt.Println("src", src, "dst", dst)
// nil vs empty
var nilSlice []int
empty := []int{}
fmt.Printf("nil==nil? %v empty==nil? %v\n", nilSlice == nil, empty == nil)
nilSlice = append(nilSlice, 1, 2)
fmt.Println("append on nil:", nilSlice)
// slices package (Go 1.21+)
nums := []int{3, 1, 2}
cloned := slices.Clone(nums)
slices.Sort(cloned)
fmt.Println("sorted clone:", cloned, "contains 2?", slices.Contains(cloned, 2))
}Expected output (illustrative; exact new cap may vary by Go version):
array a (unchanged): [1 2 3]
array copy b: [99 2 3]
s=[10 20 30] len=3 cap=3
after t[0]=200, s= [10 200 30] t= [200 30]
before grow: [1 2 3] len=3 cap=4
cap full: [1 2 3 4] len=4 cap=4
after grow: [1 2 3 4 5] len=5 cap=8
src [1 2 3] dst [7 2 3]
nil==nil? true empty==nil? false
append on nil: [1 2]
sorted clone: [1 2 3] contains 2? true
What to notice: - Assigning an array copies elements; assigning/slicing a slice shares storage. - append grows length and may allocate a larger backing array. - copy creates an independent element sequence. - slices.Sort / Clone are stdlib helpers—no third-party deps.
Try next: After t := s[1:], append enough elements to t to force reallocation, then change t[0] and see whether s still changes.