Nil slice — declared but never initialized; pointer, length, and capacity are all zero. Empty slice — has a backing array allocated but contains no elements.
Both behave the same for len(), cap(), range, and append. The key distinction appears in serialization and equality: a nil slice marshals to JSON null, while an empty slice marshals to [].
1var nilSlice []int // nil2emptySlice := []int{} // non-nil, empty3madeSlice := make([]int, 0) // non-nil, empty45fmt.Println(nilSlice == nil) // true6fmt.Println(emptySlice == nil) // false78nilJSON, _ := json.Marshal(nilSlice) // "null"9emptyJSON, _ := json.Marshal(emptySlice) // "[]"
Safely appending to a nil slice works — Go allocates the backing array on the first append. This means you can build slices from nil without special cases.
1var s []int2s = append(s, 1, 2, 3) // works perfectly3fmt.Println(s) // [1 2 3]
When to use:
slices.Equal(nilSlice, emptySlice) to be false.Common mistakes:
== against a non-nil empty slice — only nil comparison works; use slices.Equal for content.null instead of [], confusing clients.append on a nil slice is safe — unnecessary if s == nil checks add noise.