Go Slices 101
Tags: go, beginners, tutorial, programming
Published: 2022-11-05 19:54:24

## Summary
Go provides built-in support for [dynamic arrays](https://en.wikipedia.org/wiki/Dynamic_array) with the [`slice`](https://go.dev/ref/spec#Slice_types) type. A `slice` in Go is analogous to a [`list`](https://docs.python.org/3/tutorial/introduction.html#lists) in Python, an [`ArrayList`](https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html) in Java, or an [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) in JavaScript.

This post goes into the basics of slices in Go with an emphasis on common operations and their gotchas.

## Internals

The `slice` type is a wrapper around an [array](https://go.dev/ref/spec#Array_types). The underlying array is the storage, and the `slice` automatically resizes the array to fit new data. You can read more about the internals of slices [here](https://go.dev/blog/slices-intro#slice-internals).

Being mindful of this behavior is important to avoid unwanted side effects in your applications. Some "gotchas" are noted throughout in the following sections.

## Instantiating

You can create a new slice of any type using either [composite literal](https://go.dev/ref/spec#Composite_literals) syntax, or with the [`make`](https://go.dev/ref/spec#Making_slices_maps_and_channels) function.

```go
// Create a new, empty slice of integers two ways
s1 := []int{}
s2 := make([]int, 0)
```

The main difference between the two options is that composite literals allow you to initialize and pre-populate a `slice` from known elements (literals), whereas `make` allows you to declare (upfront) both the length and capacity of the `slice` (more on this in the next section).

A `slice` can be created with _any_ underlying type. See below for an example with a custom type, `Foo`.

```go
type Foo struct{
	a int
	b string
}
foos := []Foo{
	{a: 1, b: "1"},
	{a: 2, b: "2"},
}
```

## Length vs. Capacity

Using `make` allows you to specify upfront what the length and (optionally) capacity of the slice should be. When the capacity argument is not provided, it is equal to the length.

**The length of a slice is the number of populated elements, whereas the capacity is the total space allocated (in number of elements) for both populated and new (not yet populated) elements.** As was mentioned previously, a `slice` will automatically resize itself if it needs to add more data (length) but does not have enough capacity to do so.

Immediately after calling `make`, the resulting slice is initialized with `length` number of elements of the natural value (or "zero value") for the slice's type. You can read more about zero values in [this great post](https://dave.cheney.net/2013/01/19/what-is-the-zero-value-and-why-is-it-useful) by Dave Cheney. For the purpose of this post, it's enough to know that the natural values for `int` and `string` are `0` and `""` respectively.

Below we create two slices of integers. The first has a length and capacity of 3. The second has a length of 2, and capacity of 5. Immediately after calling `make`, both slices contain `length` number of integer values of `0`.

```go
s1 := make([]int, 3) // [0, 0, 0]
s2 := make([]int, 2, 5) // [0, 0]

// Appending to s2 does not create a new underlying
// array because it has  enough capacity for the new
// elements, however appending to s1 does.
s1 = append(s1, 97) // [0, 0, 0, 97]
s2 = append(s2, 98, 99, 100) // [0, 0, 98, 99, 100]
```

You can find the length and capacity of a slice using the `len` and `cap` functions respectively.

```go
s := []int{1, 2, 3}
len(s) // 3
cap(s) // 3
```

## Iteration

You can iterate over a slice using the `range` syntax (example below), or with a `for` loop over the length of the slice.

```go
s := []string{"foo", "bar", "baz"}
for i, v := range s {
	// v is the element at index position i
}
```

## Reading and Writing

To read an element from an arbitrary index position, you can use [index expressions](https://go.dev/ref/spec#Index_expressions). Below, we read the element at the second index position.

```go
s := []string{"foo", "bar", "baz"}
s[1] // "bar"
```

Similarly, to write (or assign) into an arbitrary index position we can use an [assignment](https://go.dev/ref/spec#Assignment_statements) with an index expression.

```go
s := []string{"foo", "bar", "baz"}
s[1] = "BINGO" // ["foo", "BINGO", "baz"]
```

To read or write more than one value, you can use a simple `for` loop. The example below demonstrates assigning into a `slice` at multiple positions.

```go
s := []string{"foo", "bar", "baz"}
for _, i := range []int{0, 2} {
	s[i] = "KAPOW" // Overwrites element at index i
}
// ["KAPOW", "bar", "KAPOW"]
```

## Appending

To add an element to the end of a `slice`, you can use the [`append`](https://go.dev/ref/spec#Appending_and_copying_slices) function.

```go
s := []int{5, 7, 1} // [5, 7, 1]
s = append(s, 9) // [5, 7, 1, 9]
```

Because the `append` function accepts a variadic number of elements (as it's second argument), you can append multiple items at once.

```go
s := []string{"foo"}
s = append(s, "bar", "baz")
```

To extend a slice with the contents of another slice, we can  unpack (or spread) another slice using the [`...`](https://go.dev/ref/spec#Passing_arguments_to_..._parameters) syntax.

```go
s1 := []int{1, 2}
s2 := []int{3, 4, 5}
s3 := append(s1, s2...) // [1, 2, 3, 4, 5]
```

**Gotcha:** The `append` function reuses the underlying array from the first argument when there is enough capacity to add the new elements, otherwise it creates a new one.

```go
// Create a slice of integers with capacity for
// 4 elements but only add 3 elements.
s1 := make([]int, 3, 4)
s1[0] = 1
s1[1] = 2
s1[2] = 3

// Create s2 by appending to s1, since s1
// has enough capacity to hold a new element
// s2's underlying array is the same.
s2 := append(s1, 4)

// Appending to s1 will also append to s2
// since their underlying arrays are the same!
s1 = append(s1, 99)
```

## Inserting

You can use the `append` function to insert a new element into an arbitrary position in the slice. This is done by "stretching" out the slice at the chosen index, and then directly assigning into the slice at that position.

```go
s := []int{1, 3, 4}

// Insert 2 into the second position
i := 1
s = append(s[:i+1], s[i:]...) // [1, 3, 3, 4]
s[i] = 2 // [1, 2, 3, 4]
```

## Deleting

Similar to how we can insert an element by "stretching" the slice at a given index position (and assigning), we can delete an element by "contracting" the slice at that position.

```go
s := []int{1, 2, 3, 4}

// Delete the element at the second position
i := 1
s = append(s[0:i], s[i+1:]...) // [1, 3, 4]
```

## Copying

The built-in `copy` function can be used to copy the contents of one slice, the "source", into another slice, the "destination".

**Gotcha:** The destination slice must have at least as much length as the source slice for _all_ elements to be copied. Otherwise, only the minimum of `len(destination)` and `len(source)` elements will be copied. You can read a great explanation of this [here](https://stackoverflow.com/a/32645098). 

In the example below, we copy from `s1` (the source) into `s2` (the destination). Because `s2` has 4 elements, but `s1` only has 3, the result is that all 3 elements are copied into `s2` but `s2` still retains the rest of its data.

```go
// Copy the contents of s1 into s2, and len(s1) < len(s2)
s1 := []int{1, 2, 3}
s2 := []int{4, 5, 6, 7}
copy(s2, s1)
// s2 is now [1, 2, 3, 7]
```

In the next example, we perform a basic copy from one slice into a new slice that has been created specifically with enough space to fit the entire copy.

```go
s1 := []int{1, 2, 3}
s2 := make([]int, len(s1)) // [0, 0, 0]
copy(s2, s1)
// s2 is now [1, 2, 3]
```

You can read more about copying as well as appending [here](https://go.dev/ref/spec#Appending_and_copying_slices).

## Slice Expressions

[Slice expressions](https://go.dev/ref/spec#Slice_expressions) allow you to "slice" a `slice`. Essentially extracting a subsequence of the elements in the slice.

The most commonly used syntax is of the form `[low : high]` where `low` is the starting index (inclusive), and high is the ending index (exclusive).

In the example below, we extract the range of elements starting from the second index position, and ending just before the fifth index position. 

```go
s := []int{1, 2, 3, 4, 5}
s2 := s[1:4] // [2, 3, 4]
```

**Gotcha:** In the above example, `s2` uses the same underlying array as `s1` so updates to `s2` can reflect in `s1`, e.g. `s2[0] = 99` will update `s1[1]` to 99.

There is a less commonly used syntax of the form `[low : high : max]` where `max` is used to set the capacity on the resulting slice.

For example, below use the same slice expression, but the resulting slice has capacity `3` (since `max - low == 4 - 1`), so appending to `s2` would force a new array allocation (then different from the array use to back `s1`).

```go
s := []int{1, 2, 3, 4, 5}
s2 := s[1:4:4] // [2, 3, 4]
```

## Nil Slices

The zero value for a `slice` is `nil`. Slices can be nil if you declare but don't initialize them, e.g. `var s []int` (or if you re-assign a `slice` variable directly to `nil`.

One of the nice things about slices in Go is that calling `len` or `cap` on a nil slice does not panic (they both simply return 0). A consequence of that is you don't have to guard against a nil pointer panic when performing `range` iteration over a possibly nil slice. However, you can't read or write into a `nil` slice.

```go
var s []int // s is a nil slice
for i, v := range s {
	// Loop body is never executed since s has no elements
}
s[0] = 1 // panic
```