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 change the size of a slice using the
append
function. - Reference Type: A slice holds a reference to an underlying array. When you assign a slice to a new variable, you are copying the slice header, but not the underlying data. Both slices will point to the same 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 | Passed by value (a full copy is made). | Passed by reference (the slice header is copied). |
Flexibility | Low. | High. Can be resliced, appended to, etc. |
Use Case | When you need a fixed number of elements. | Most of the time. It’s the idiomatic choice in Go. |
When to Use Which?
- Use arrays when you have a fixed collection of elements and you want to prevent any accidental modification of the size. A good example is representing a color with RGB values:
var color [3]int
. - Use slices for almost everything else. Their flexibility and efficiency make them the go-to data structure for collections in Go. If you’re not sure which one to use, you probably want a slice.
Understanding the distinction between arrays and slices is a key step in mastering Go. While arrays have their place, slices offer the power and flexibility needed for most programming tasks.