Nil slice — zero value of a slice type, no backing array allocated. Empty slice — has a backing array but length is 0.
A nil slice has len == 0, cap == 0, and its internal pointer is nil. It is the default zero value when you declare a slice without initializing it. You can still use append on a nil slice safely — Go allocates a backing array automatically.
1var s []int // nil slice2fmt.Println(s == nil) // true3fmt.Println(len(s)) // 04s = append(s, 1) // works! now s = [1]
An empty slice has a non-nil pointer to a backing array. Both have len == 0 and behave identically in almost all operations — the difference shows up in JSON serialization and equality checks.
1e := []int{} // empty slice (non-nil)2m := make([]int, 0) // also empty slice3fmt.Println(e == nil) // false4fmt.Println(m == nil) // false
JSON behavior:
1data, _ := json.Marshal(nilSlice) // "null"2data, _ := json.Marshal(emptySlice) // "[]"
When to use:
Common mistakes:
len(s) == 0 without also checking s == nil when nil vs empty matters (e.g., API responses).nil when the caller expects [] in JSON — causes null vs empty mismatch.range — this is safe and executes zero iterations, but beginners often think it panics.