Collections store several values together. Choose an array when the length is part of the type, a slice for a sequence whose length can change, and a map for looking up values by key. Strings store bytes and have their own rules for text. This lesson focuses on choosing the right container and predicting which changes are shared.
Arrays and slices represent different things
[3]int is an array of exactly three integers. [4]int is a different type. Assigning an array copies its elements. A slice, written []int, is a small value describing a region of an underlying array. It records a length and capacity along with access to that storage.
len(s) counts accessible elements. cap(s) describes how far the slice can extend within its backing array. append may reuse storage or allocate another array; always use its returned slice. Do not write code whose correctness depends on an assumed capacity growth factor. See Go Slices: usage and internals.
Run a collection walkthrough
Create a fresh module, save this as main.go, and run go run .:
package main
import (
"fmt"
"sort"
"strings"
"unicode/utf8"
)
func main() {
fixed := [3]int{1, 2, 3}
arrayCopy := fixed
arrayCopy[0] = 9
fmt.Println("arrays:", fixed, arrayCopy)
items := []string{"read", "code", "test"}
window := items[:2]
window[0] = "plan"
independent := make([]string, len(items))
copy(independent, items)
independent[1] = "rest"
items = append(items, "ship")
fmt.Println("items:", items)
fmt.Println("copy:", independent)
counts := make(map[string]int)
for _, word := range strings.Fields("go go code") {
counts[word]++
}
keys := make([]string, 0, len(counts))
for key := range counts {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
fmt.Printf("%s=%d\n", key, counts[key])
}
_, found := counts["rust"]
fmt.Println("rust present:", found)
text := "Go界"
fmt.Println("text:", len(text), utf8.RuneCountInString(text))
for index, r := range text {
fmt.Printf("%d:%c ", index, r)
}
fmt.Println()
}
Expected output, ignoring the final line's trailing space:
arrays: [1 2 3] [9 2 3]
items: [plan code test ship]
copy: [plan rest test]
code=1
go=2
rust present: false
text: 5 3
0:G 1:o 2:界
The slice window shares elements with items, so changing its first element changes items. The explicit copy puts string elements in separate slice storage, so changing independent[1] does not change items[1]. This is an element copy, not a recursive deep copy: elements containing pointers or slices could still refer to shared data.
Maps and the missing-key question
A map associates comparable keys with values. Strings and integers are common key types; slices cannot be keys. Looking up a missing key returns the value type's zero value. The two-result form, value, ok := counts[key], distinguishes “missing” from “present with zero.” Incrementing a missing word's count works because the initial integer value is zero.
Map iteration order is unspecified. The example sorts a separate key slice so its displayed output is predictable. Use delete(counts, key) to remove a key. Assigning one map variable to another shares the underlying map; it does not duplicate entries. The official maps article covers these behaviors.
A nil slice can be ranged over and appended to. A nil map can be read, ranged over, and passed to delete, but assigning an entry panics. Initialize writable maps with make or a map literal. Neither a normal map nor a shared slice becomes safe for concurrent mutation merely because its operations look small.
Strings, bytes, and runes
A Go string is an immutable sequence of bytes. len(text) counts bytes, while text[i] retrieves one byte. In UTF-8, a Unicode code point may occupy multiple bytes. Ranging over a string decodes runes, with indices referring to byte positions. That explains why "Go界" has five bytes but three runes.
Use []rune(text) when your operation needs code points, and []byte(text) when it needs bytes. A displayed character can contain several code points, such as a letter plus a combining mark; counting runes is not a universal count of visible characters. Strings can also contain invalid UTF-8. The strings and runes article explains these distinctions.
Pitfalls and boundary checks
The upper bound in items[a:b] is excluded, and invalid bounds panic. Ranging over a slice produces copies of its element values; assigning to the loop variable does not replace the stored element. Use the index to update the slice. Also remember that an empty slice and a nil slice both have length zero, but some encodings distinguish them.
Exercise: count normalized words
Count words in "Go go CODE" without distinguishing letter case, then print the counts alphabetically. Also try empty input. Keep punctuation handling out of scope for this exercise.
Solution guidance
Apply strings.ToLower to the input before strings.Fields, then reuse the map and sorted-key approach. The result should be code=1 and go=2. Empty input should produce no count lines. For a further check, copy a slice, mutate one element, and explain whether you used assignment or independent element storage.
Previous: Functions and Defer. Next: Pointers, Structs, and Methods.

