0
0
GoComparisonBeginner · 4 min read

Nil Slice vs Empty Slice in Go: Key Differences and Usage

In Go, a nil slice is a slice with no underlying array and its value is nil, while an empty slice has an allocated array but zero length. Both behave similarly in many cases, but nil slices are nil and empty slices are not, which affects comparisons and JSON encoding.
⚖️

Quick Comparison

Here is a quick comparison of key aspects between nil slice and empty slice in Go.

AspectNil SliceEmpty Slice
Underlying arrayNone (no allocation)Allocated (zero length)
ValuenilNon-nil (empty)
Length00
Capacity0≥ 0 (depends on allocation)
Equality checkEqual to nilNot equal to nil
JSON encodingEncodes as nullEncodes as empty array []
⚖️

Key Differences

A nil slice in Go means the slice variable points to no underlying array and its value is literally nil. It has zero length and zero capacity. You get a nil slice when you declare a slice without initializing it, for example: var s []int. This slice is ready to use but contains no elements and no memory allocated.

An empty slice is a slice with an allocated underlying array but zero length. It is created by initializing a slice with zero elements, for example: s := []int{} or make([]int, 0). It is not nil, so s == nil is false, but it behaves like a slice with no elements.

These differences matter in some cases like JSON encoding, where nil slices encode as null and empty slices encode as empty arrays []. Also, checking if a slice is nil is a common way to test if it was initialized, which does not work for empty slices.

⚖️

Code Comparison

This example shows how a nil slice behaves when declared and used.

go
package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	var nilSlice []int

	fmt.Println("nilSlice == nil:", nilSlice == nil) // true
	fmt.Println("len(nilSlice):", len(nilSlice))     // 0
	fmt.Println("cap(nilSlice):", cap(nilSlice))     // 0

	jsonData, _ := json.Marshal(nilSlice)
	fmt.Println("JSON encoding of nilSlice:", string(jsonData)) // null
}
Output
nilSlice == nil: true len(nilSlice): 0 cap(nilSlice): 0 JSON encoding of nilSlice: null
↔️

Empty Slice Equivalent

This example shows how an empty slice behaves when created and used.

go
package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	emptySlice := []int{}

	fmt.Println("emptySlice == nil:", emptySlice == nil) // false
	fmt.Println("len(emptySlice):", len(emptySlice))     // 0
	fmt.Println("cap(emptySlice):", cap(emptySlice))     // 0

	jsonData, _ := json.Marshal(emptySlice)
	fmt.Println("JSON encoding of emptySlice:", string(jsonData)) // []
}
Output
emptySlice == nil: false len(emptySlice): 0 cap(emptySlice): 0 JSON encoding of emptySlice: []
🎯

When to Use Which

Choose a nil slice when you want to represent an uninitialized or absent slice, especially if you want JSON encoding to produce null. It is also slightly more memory efficient since it has no underlying array.

Choose an empty slice when you want to represent a slice that is intentionally empty but initialized, so that operations like appending work without checks. It also encodes to an empty array [] in JSON, which can be clearer for APIs expecting arrays.

In general, both behave similarly in loops and indexing, so pick based on semantic meaning and encoding needs.

Key Takeaways

A nil slice has no underlying array and equals nil; an empty slice has an allocated array but zero length.
Nil slices encode as null in JSON; empty slices encode as empty arrays [].
Use nil slices to represent uninitialized or missing data.
Use empty slices to represent initialized but empty collections.
Both have zero length and behave similarly in most slice operations.