I'm currently available for contract work & technical writing opportunities. Hire me

The difference between nil and empty slices in Go

The difference between nil and empty slices are a common point of confusion for newbies to Go. This post discusses how they differ and when to use each one.

Slices are one of the most common data types used in Go. They provide a way for to work with and manage collections of data, and can be appended to or iterated over. But what’s a nil slice and how does it differ from an empty slice? Let’s find out.

Nil slices

A nil slice is one that is declared without being initialised. It has a length and capacity of 0 with no underlying array, and it’s zero value is nil. Nil slices are useful to represent a collection that does not exist yet.

// nil slice
var slice []string
slice == nil // true

Empty slice

An empty slice is one that contains zero elements. It has an underlying array, but with zero elements. An empty slice is handy for representing an empty collection, such as when a query yields no results.

// Empty slice
var slice = []string{}
// or
var slice = make([]string, 0)
slice == nil // false

What’s the difference?

For the most part, there’s actually no observable difference between nil slices and empty slices. The built-in functions append, len, and cap all work in the same way for both, and you can for...range over either with the same result (0 iterations).

var s1 []int
s2 := []int{}

fmt.Println(len(s1)) // 0
fmt.Println(len(s2)) // 0

s1 = append(s1, 1)
s2 = append(s2, 2)

fmt.Println(len(s1)) // 1
fmt.Println(len(s2)) // 1

There’s a subtle difference though. As mentioned earlier, nil slices are great for collections that do not exist yet, while empty slices are for empty collections. This is exemplified when encoding to JSON:

// https://play.golang.org/p/PFUHW5d-26u
package main

import (
	"encoding/json"
	"fmt"
)

type Response struct {
	Result []int `json:"result"`
}

func main() {
	// nil slice
	var s1 []int
	r1 := &Response{
		Result: s1,
	}

	b, _ := json.Marshal(r1)
	fmt.Printf("%+v\n", string(b)) // {"result":null}

	s2 := []int{}
	r2 := &Response{
		Result: s2,
	}

	b, _ = json.Marshal(r2)
	fmt.Printf("%+v\n", string(b)) // {"result":[]}
}

As you can see, the nil slice gets encoded as null while the empty slice becomes an empty JSON array. You need to guard against returning the wrong value from your API, as it could easily violate your API spec leading to bugs and unhappy customers.

Thanks for reading, and happy coding!