Slice

A slice in Go is a dynamic array that can store elements of the same type. It can be indexed and its length queried, much like a fixed-size array. Unlike arrays, slices can grow or shrink as needed. When the capacity is exceeded, a new underlying array is allocated, and the data is transferred.

General syntax

list := []Type{}

Its default value is nil or a nil slice.

If you don't define size to an array, Go will handle it as a slice.

Here are some example how you can create a slice type:

package main
 
import (
	"fmt"
	"reflect"
)
 
func main() {
	// similar to defining an array, except without a predefined length
	slice1 := []int{1, 2}
	fmt.Println(reflect.TypeOf(slice1).Kind().String()) // slice
 
	array := [6]int{2, 3, 5, 7, 11, 13} // that creates a fixed size array
	// creating from array
	var slice2 []int = array[1:4]
 
	// creating by using make with a length of 5
	slice3 := make([]int, 5)
 
	// using make and defining capacity allocates an underlying array
	// of size 10 and returns a slice of length 0 and capacity 10
	slice4 := make([]int, 0, 10)
 
	fmt.Println("slice1", slice1)
	fmt.Println("slice2", slice2)
	fmt.Println("slice3", slice3)
	fmt.Println("slice4", slice4)
}

You can run the above code here (opens in a new tab).

Its default value is nil or a nil slice, as it's not possible to assign the nil value of another type.

length := len(array)

The default length will be zero.

Individual elements can be accessed using indexing, similar to an array.

item := slice[1]

Adding an Item to a nil Slice in Go

In Go, a nil slice is a slice that has been declared but not initialized with any values. Despite being nil, you can still use the built-in append function to add items to it. When you append an item to a nil slice, Go automatically allocates memory for the slice and adds the item to it. As a result, the slice will no longer be nil and will have a length of 1.

Here's an example to demonstrate this behavior:

package main
 
import (
	"fmt"
)
 
func main() {
	var list []int = nil // Declare a nil slice of int
 
	list = append(list, 1) // Append an item to the nil slice
 
	fmt.Println(list) // Output: [1]
}

In this example, list is initially a nil slice. After appending the integer 1 to it, the slice becomes [1] with a length of 1. This demonstrates that the append function can handle nil slices gracefully, making it easy to work with slices that may not be initialized at the time of declaration.

Play with the example here (opens in a new tab)

slices package

Go Team offers a Go package called slices, which is remarkably useful for slice operations. You can find the official doc here (opens in a new tab), I will review couple of the functions and discuss them.

Delete function

The Delete function zeroes out the elements of a given slice within a specified interval and adjusts the slice's length accordingly. In the example below, I demonstrate that the capacity of the slice remains unchanged.

package main
 
import (
	"fmt"
 
	"Go.org/x/exp/slices"
)
 
func main() {
	slice := []int{1, 2, 3, 4, 5, 6, 7}
	fmt.Printf("before delete len=%d cap=%d\n", len(slice), cap(slice))
	fmt.Println(slice)
 
	slice = slices.Delete(slice, 0, 5)
 
	fmt.Printf("after delete len=%d cap=%d\n", len(slice), cap(slice))
	fmt.Println(slice)
}

Run the code here (opens in a new tab)

DeleteFunc function

The DeleteFunc function zeroes out the elements of a given slice if they meet a specified condition. It does not change the capacity of the slice.

package main
 
import (
	"fmt"
 
	"Go.org/x/exp/slices"
)
 
func main() {
	slice := []int{1, 2, 3, 4, 5, 6, 7}
	fmt.Printf("before delete len=%d cap=%d\n", len(slice), cap(slice))
	fmt.Println(slice)
 
	slice = slices.DeleteFunc(slice, func(i int) bool {
		return i > 3
	})
 
	fmt.Printf("after delete len=%d cap=%d\n", len(slice), cap(slice))
	fmt.Println(slice)
}

Play with the code here (opens in a new tab)

Clip function

The Clip function trims any unused capacity from the given slice, reducing its size to match its current length. In contrast, the Delete and DeleteFunc functions do not modify the underlying array; they only zero out the values. These functions can be useful for managing the size of the underlying array without altering its capacity.

package main
 
import (
	"fmt"
 
	"Go.org/x/exp/slices"
)
 
func main() {
	slice := []int{1, 2, 3, 4, 5, 6, 7}
	fmt.Printf("before delete len=%d cap=%d\n", len(slice), cap(slice))
	fmt.Println(slice)
 
	slice = slices.DeleteFunc(slice, func(i int) bool {
		return i > 3
	})
 
	fmt.Printf("after delete len=%d cap=%d\n", len(slice), cap(slice))
	fmt.Println(slice)
 
	slice = slices.Clip(slice)
	fmt.Printf("after clip len=%d cap=%d\n", len(slice), cap(slice))
	fmt.Println(slice)
}

Play with the code here (opens in a new tab)

Grow function

The Grow function increases the capacity of a slice by at least the specified value. The underlying array of the slice will typically expand when all its fields are populated and a new element needs to be appended. Growing the underlying array involves linear complexity, as it requires assigning each value to the new array. This function allows us to control when this performance impact occurs

package main
 
import (
	"fmt"
 
	"Go.org/x/exp/slices"
)
 
func main() {
	slice := []int{1, 2, 3, 4, 5, 6, 7}
	fmt.Printf("before grow len=%d cap=%d\n", len(slice), cap(slice))
	fmt.Println(slice)
 
	slice = slices.Grow(slice, 10)
	fmt.Printf("after grow len=%d cap=%d\n", len(slice), cap(slice))
	fmt.Println(slice)
}

Test the code here (opens in a new tab)