3 minutes
Arrays vs. Slices in Go: What’s the Difference?
When you’re new to Go, one of the first data structures you’ll encounter are arrays and slices. They look similar, but they have fundamental differences that are crucial to understand. Using the wrong one can lead to bugs or performance issues. Let’s break it down.
Arrays
An array in Go is a fixed-size sequence of elements of the same type. The size of the array is part of its type. This means that [4]int and [5]int are two different, incompatible types.
Here’s how you declare an array:
// An array of 4 integers, initialized to their zero value (0)
var a [4]int
// An array with initial values
b := [3]string{"one", "two", "three"}
Key Characteristics of Arrays:
- Fixed Size: Once you declare an array, you cannot change its size.
- Value Type: When you assign an array to a new variable or pass it to a function, you are creating a copy of the entire array. This can be inefficient for large arrays.
func main() {
a1 := [3]int{1, 2, 3}
a2 := a1 // a2 is a copy of a1
a2[0] = 99
fmt.Println("a1:", a1) // Output: a1: [1 2 3]
fmt.Println("a2:", a2) // Output: a2: [99 2 3]
}
Slices
A slice, on the other hand, is a dynamically-sized, flexible view into the elements of an array. A slice is much more common in Go programs than an array.
A slice is a descriptor of an array segment. It consists of three components:
- A pointer to the underlying array.
- The length of the segment.
- The capacity (the maximum length the segment can reach).
Here’s how you create a slice:
// A slice of integers
var s []int
// A slice created from an array
a := [5]int{1, 2, 3, 4, 5}
s = a[1:4] // s now holds {2, 3, 4}
// A slice literal
s2 := []string{"a", "b", "c"}
Key Characteristics of Slices:
- Dynamic Size: You can grow a slice using the
appendfunction. - Slice Header (Pointer Semantics): A slice holds a pointer to an underlying array. When you assign a slice to a new variable or pass it to a function, Go copies the 3-word slice header (pointer, length, capacity), not the underlying elements. Both slices still point to the same backing array.
func main() {
s1 := []int{1, 2, 3}
s2 := s1 // s2 refers to the same underlying array as s1
s2[0] = 99
fmt.Println("s1:", s1) // Output: s1: [99 2 3]
fmt.Println("s2:", s2) // Output: s2: [99 2 3]
}
Main Differences Summarized
| Feature | Array | Slice |
|---|---|---|
| Size | Fixed, defined at compile time. | Dynamic, can grow and shrink. |
| Type | The size is part of the type. | The size is not part of the type. []int is one type. |
| Passing | Copies the entire array. | Copies only the slice header (shares backing array). |
| Flexibility | Low. | High. Can be resliced, appended to, etc. |
| Use Case | Fixed-size buffers, keys, RGB/UUIDs. | Almost all collections in Go. |
When to Use Which?
- Use arrays when the exact length is part of the domain type or you want value-copy semantics without heap allocations (for example, a 16-byte UUID
[16]byteor RGB triplet[3]uint8). - Use slices for almost everything else.