Nil Slice vs Empty Slice in Go: Key Differences and Usage
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.
| Aspect | Nil Slice | Empty Slice |
|---|---|---|
| Underlying array | None (no allocation) | Allocated (zero length) |
| Value | nil | Non-nil (empty) |
| Length | 0 | 0 |
| Capacity | 0 | ≥ 0 (depends on allocation) |
| Equality check | Equal to nil | Not equal to nil |
| JSON encoding | Encodes as null | Encodes 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.
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 }
Empty Slice Equivalent
This example shows how an empty slice behaves when created and used.
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)) // [] }
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.