Array — fixed-length, value-type container with length baked into the type. Slice — a lightweight, dynamic view over a contiguous block of memory.
Arrays are assigned by value — the entire array is copied. This can be expensive for large arrays but makes them safe to use without worrying about shared state.
1arr := [4]string{"a", "b", "c", "d"}2copy := arr3copy[0] = "z"4fmt.Println(arr[0]) // "a" — original unchanged
Slices are three words in memory: a pointer, length, and capacity. When you slice an existing array or slice, the new slice shares the backing memory. This makes slicing efficient but means mutations can affect the original.
1original := []string{"a", "b", "c", "d"}2view := original[1:3] // ["b", "c"] — shares memory3view[0] = "z"4fmt.Println(original[1]) // "z" — original changed!56// append may allocate new backing array7view = append(view, "x") // may or may not affect original
When to use:
Common mistakes:
append — append(s, elem) returns a new slice header.copy() to deep-copy slices — it copies elements but shares the underlying array.make([]int, 10) expecting empty slice with capacity — it has len=10.