The Difference between Nil and Empty Slices in Go

Slices are a fundamental data type in Go, essential for handling collections of data. They can be dynamically appended or iterated over. Understanding the distinction between a nil slice and an empty slice is key for Go beginners.

Nil slices

A nil slice hasn’t been initialized and thus points to no underlying array. It’s characterized by a length and capacity of 0, and its zero value is nil. Nil slices are ideal for indicating a yet-to-be-created collection.

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

Empty slice

Conversely, an empty slice, although it contains no elements, is initialized and does have an underlying array. It’s useful for denoting an empty collection, like the result of a query with no matches.

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

Key differences between nil and empty slices

Functionally, nil and empty slices behave similarly with Go’s built-in functions (such as append(), len(), and cap()) and can both be used in for...range loops with no iterations.

go
var s1 []int // nil slice
s2 := []int{} // empty slice

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

The primary distinction lies in their representation: nil slices suggest a non-existent collection, while empty slices represent a collection without elements. This difference becomes clear when encoding slices as JSON:

go
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))

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

	b, _ = json.Marshal(r2)
	fmt.Printf("%+v\n", string(b))
}

The nil slice gets encoded as null while the empty slice becomes an empty JSON array.

output
{"result":null}
{"result":[]}

This distinction is crucial for API design to avoid unintentional errors in data representation.

Thank you for reading, and happy coding!