Nil slice — declared but never initialized; pointer, length, and capacity are all zero. Empty slice — has a backing array allocated but contains no elements; len == 0 but cap may be > 0.
Both behave identically for len, cap, range, and append. The distinction matters for JSON serialization and equality comparisons. A nil slice marshals to JSON null, while an empty slice marshals to [].
1var nilS []int // nil2emptyS := []int{} // non-nil, empty3madeS := make([]int, 0, 10) // non-nil, empty, cap=1045fmt.Println(nilS == nil) // true6fmt.Println(emptyS == nil) // false7fmt.Println(madeS == nil) // false89fmt.Println(len(nilS), cap(nilS)) // 0 010fmt.Println(len(emptyS), cap(emptyS)) // 0 011fmt.Println(len(madeS), cap(madeS)) // 0 10
JSON marshaling:
1n, _ := json.Marshal(nilS) // "null"2e, _ := json.Marshal(emptyS) // "[]"
Safely appending to a nil slice works — Go allocates the backing array on the first append.
When to use:
[] not null; initializing from a known source.Common mistakes:
null instead of [], confusing clients.len(s) == 0 when nil vs empty matters.append(nilSlice, ...) panics — it doesn't, Go handles it safely.slices.Equal(nilS, emptyS) — returns false even though both are empty.