Nil slice — var s []T or var s []T = nil; pointer is nil, length and capacity are 0. Empty slice — s := []T{} or make([]T, 0); has a backing array but no elements.
For most operations they behave identically: len, cap, range, and append all work the same way. The distinction matters for JSON serialization and equality comparisons.
1nilSlice := var ([]int) // declared inline conceptually2var nilS []int3emptyS := []int{}4madeS := make([]int, 0)56// All have len == 07fmt.Println(len(nilS), len(emptyS), len(madeS)) // 0 0 08// Only nilS == nil is true9fmt.Println(nilS == nil) // true10fmt.Println(emptyS == nil) // false
JSON marshaling behavior:
1n, _ := json.Marshal(nilS) // []byte("null")2e, _ := json.Marshal(emptyS) // []byte("[]")3m, _ := json.Marshal(madeS) // []byte("[]")
When to use:
[] not null; initializing from a known source.Common mistakes:
null instead of [].len(s) == 0 when nil vs empty matters.append(nilSlice, ...) panics — it doesn't, Go handles it.slices.Equal to compare a nil and empty slice — they are not equal even though len matches.