Slices in Go

Slices in Go

In this tutorial, we are going to discuss on slices in Go language. A slice is a data type in Go that is a mutable, or changeable, ordered sequence of elements

A slice is just like an array which is a container to hold elements of the same data type but slice can vary in size.

Syntax to define a slice is pretty similar to that of an array but without specifying the elements count. Hence s is a slice.

var s []int

Above code will create a slice of data type int that means it will hold elements of data type int.

But what is a zero-value of a slice? As we saw in arrays, zero value of an array is an array with all its elements being zero-value of data type it contains.

Like an array of int with size n will have n zeroes as its elements because of zero value of int is 0. But in the case of slice, zero value of slice defined with the syntax above is nil.

package main

import "fmt"

func main() {
    var s []int
    fmt.Println(s == nil)
}

Output

true

Run in playground

But why nil though, you ask. Because slice is just a reference to an array.

slice is a reference to an array

This may sound weird, but slice does not contain any data. It rather stores data in an array. But then you may ask, how that is even possible when array length is fixed?

slice when needed to store more data, creates a new array of appropriate length behind the scene to accommodate more data.

When a slice is created by simple syntax var s []int, it is not referencing an array, hence its value is nil. Let’s now look at how it references an array.

package main

import "fmt"

func main() {
     // define empty slice
     var s []int
     fmt.Println("s == nil", s == nil) // create an array of
     int a := [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9} 
     // creates new slice 
     s = a[3:7] 
     fmt.Println("s == nil", s == nil, "and s = ", s)
 }

Output

s == nil true 
s == nil false and s =  [40 50 60 70]

Run in playground

In the above example, we have defined a slice s of type int but this slice doesn’t reference an array. Hence, it is nil and first Println statement will print true.

Later, we created an array a of type int and assigned s with a new slice returned from a[3:7]. a[3:7] syntax returns a slice from the array a starting from 3 index element to 7 index element.

In the above example, let’s change the value of the 3rd and 4th element of the array a (index 2 and 3 respectively) and check the value of the slice s.

package main

import "fmt"

func main() {
     var s []int
     a := [...]int{10, 20, 30, 40, 50, 60, 70, 80, 90}
     s = a[3:7]
     a[3] = 45 
     a[4] = 55 
     a[5] = 65 
     fmt.Println(s)
 }

Output

[45 55 65 70]

Run in playground

From the above result, we are convinced that slice indeed is just a reference to an array and any change in that array will reflect in the slice.

Length and Capacity of a slice

As we have discussed from the arrays tutorial, to find of the length of a data type, we use len function. We are using the same len function for slices as well.

package main

import "fmt"

func main() {
     var s []int
     a := [...]int{10, 20, 30, 40, 50, 60, 70, 80, 90}
     s = a[3:7]
     fmt.Println("Length of s =", len(s))
 }

Output

Length of s = 4

Run in playground

The capacity of a slice is the number of elements it can hold. Go provides a built-in function cap to get this capacity number.

package main

import "fmt"

func main() {
    var s []int
    a := [...]int{10, 20, 30, 40, 50, 60, 70, 80, 90}
    s = a[2:4]
    fmt.Println("Capacity of s =", cap(s))
}

Output

Capacity of s = 7

Run in playground

Above program returns 7 which is the capacity of the slice. Since slice references an array, it could have referenced array till the end. Since starting from the index 2 in the above example, there are 7 elements in the array, hence the capacity of the array is 7.

Does that mean we can grow slice beyond its natural capacity? Yes, you can. We will find that out with append function.

append function

You can append new values to the slice using built-in append function. Signature of append function is

func append(slice []Type, elems ...Type) []Type

This means that append function takes a slice as the first argument, one/many elements as further arguments to append to the slice and returns a new slice of the same data type.

package main

import "fmt"

func main() {
    a := [...]int{10, 20, 30, 40, 50, 60, 70, 80, 90}
    s := a[2:4]
    newS := append(s, 55, 65)
    fmt.Println("s = ", s, "newS = ", newS)
    fmt.Println("length = ", len(newS),"capacity = ", cap(newS))
    fmt.Println("a = ", a)
}

Output

s =  [30 40] newS =  [30 40 55 65] 
length =  4 capacity =  7 
a =  [10 20 30 40 55 65 70 80 90]

As we can see from the above result, s remains unchanged and two new elements got copied to newS but look what happened to the array a. It got changed. append function mutated array referenced by slice s.

This is absolutely horrible. Hence slices are no easy business. Use append only to self assign the new slice like s = append(s, ...) which is more manageable.

What will happen if I append more elements than the capacity of a slice?

package main

import "fmt"

func main() {
     a := […]int{10, 20, 30, 40, 50, 60, 70, 80, 90}
     s := a[2:4]
     fmt.Printf("before => s=%v\n", s)
     fmt.Printf("before => a=%v\n", a)
     fmt.Printf("before => len=%d, cap=%d\n", len(s), cap(s))
     fmt.Println("&a[2] == &s[0] is", &a[2] == &s[0])
 
     s = append(s, 100, 110, 120, 130, 140, 150, 160) 
     fmt.Printf("after => s=%v\n", s) 
     fmt.Printf("after => a=%v\n", a) 
     fmt.Printf("after => len=%d, cap=%d\n", len(s), cap(s)) 
     fmt.Println("&a[2] == &s[0] is", &a[2] == &s[0])
 }

Output

before => s=[30 40] 
before => a=[10 20 30 40 50 60 70 80 90] 
before => len=2, cap=7 
&a[2] == &s[0] is true 
after => s=[30 40 100 110 120 130 140 150 160]
after => a=[10 20 30 40 50 60 70 80 90] 
after => len=9, cap=14 
&a[2] == &s[0] is false

Run in playground

So first we created an array a of int and initialized with a bunch of values. Then we created the slice s from array a starting from index 2 to 3.

From the first set of Print statements, we verified values of s and a. Then we made sure that s references array a by matching the memory address of their respective elements. The length and capacity of the slice s is also convincing.

Then we appended the slice s with 7 more values. So we expect the slice s to have 9 elements, hence its length is 9 but we have no idea about its new capacity. Now we found that slice s got bigger than its initial capacity of 7 to 14 and its new length is 9. But array a remain unchanged.

This looks weird at first but kinda amazing. Go figures out the math on its own that we are trying to push more values to the slice that its underneath array can’t hold, so it creates a new array with greater length and copies old slice values to it. Then new values from append is added to that array and origin array remain unchanged as no operation was done on it.

anonymous array slice

Until now, we saw a slice that references an array we defined deliberately. But almost all the time, you would go with an array that is hidden and not accessible to the public.

Similar to an array, slice can be defined in a similar fashion with an initial value. In this case, Go will create a hidden array to contain the values.

package main

import "fmt"
func main() {
    s := []int{10, 20, 30, 40, 50}
    fmt.Println("s = ", s) 
    fmt.Printf("Length = %d, Capacity = %d", len(s), cap(s))
}

Output

s = [10 20 30 40 50] 
Length = 5, Capacity = 5

Run in playground

It’s pretty obvious that the capacity of this slice is 5 because the array is created by Go and Go preferred creating an array of length 5 as we are creating a slice of 5 elements. But what will happen when we append more two elements.

package main

import "fmt"

func main() {
    s := []int{10, 20, 30, 40, 50}
    s = append(s, 60, 70, 80)
    fmt.Println("s = ", s) 
    fmt.Printf("Length = %d, Capacity = %d", len(s), cap(s))
}

Output

s =  [10 20 30 40 50 60 70 80] 
Length = 8, Capacity = 10

Run in playground

So, Go created an array of 10 length because when we are pushing 3 new elements to the slice, the original array of length 5 was not enough to hold 8 elements. No new array will be created if we appended new elements to the slice unless slice exceeds the length of 10.

Slices in Go

Scroll to top