On this page
Slices and Maps
Arrays, Slices and Maps: Go's Collections
Go has three fundamental collection types: arrays (rarely used directly), slices (the most-used collection), and maps (statically-typed hash tables). Understanding how they work internally is key to writing efficient, bug-free Go code.
Arrays: Fixed-Size Collections
An array in Go has a fixed size that is part of its type. [3]int and [5]int are completely different types:
// Declaration
var a [5]int // [0 0 0 0 0]
b := [3]string{"a", "b", "c"} // [a b c]
c := [...]int{1, 2, 3, 4, 5} // size inferred: [5]int
// Access
fmt.Println(b[0]) // "a"
b[1] = "z"
// Iterate
for i, v := range c {
fmt.Printf("c[%d] = %d\n", i, v)
}Arrays are value types: assigning them or passing them as parameters makes a complete copy. For this reason, in practice you almost always use slices.
Slices: Go's Dynamic Collection
A slice is a view over an underlying array. It has three properties:
- Pointer: to the first element of the underlying array
- Length (
len): number of accessible elements - Capacity (
cap): number of elements from the pointer to the end of the underlying array
// Several ways to create a slice
s1 := []int{1, 2, 3, 4, 5} // literal
s2 := make([]int, 5) // [0 0 0 0 0], len=5, cap=5
s3 := make([]int, 3, 10) // [0 0 0], len=3, cap=10
var s4 []int // nil slice
fmt.Println(len(s1), cap(s1)) // 5 5
fmt.Println(len(s2), cap(s2)) // 5 5
fmt.Println(len(s3), cap(s3)) // 3 10
fmt.Println(s4 == nil) // true`append`: Adding Elements
append is the fundamental function for slices. If the capacity is sufficient, it adds to the same array. If not, it allocates a new, larger array (typically doubling the capacity) and copies the elements:
s := []int{1, 2, 3}
s = append(s, 4) // [1 2 3 4]
s = append(s, 5, 6, 7) // [1 2 3 4 5 6 7]
// Append another slice
other := []int{8, 9, 10}
s = append(s, other...) // [1 2 3 4 5 6 7 8 9 10]
// Always assign the result of append
s = append(s, 11) // NOT: append(s, 11) without assigningSlicing: Creating Sub-slices
s := []int{0, 10, 20, 30, 40, 50}
// s[start:end] — end is exclusive
s1 := s[1:4] // [10 20 30], len=3, cap=5
s2 := s[:3] // [0 10 20], from the start
s3 := s[3:] // [30 40 50], to the end
s4 := s[:] // reference to the same underlying array
// Three-index slicing: control the capacity of the sub-slice
s5 := s[1:3:4] // [10 20], len=2, cap=3Caution: Sub-slices Share Memory
original := []int{1, 2, 3, 4, 5}
sub := original[1:3] // [2 3], shares array with original
sub[0] = 99
fmt.Println(original) // [1 99 3 4 5] — original was modified!
// To avoid this, use copy
clone := make([]int, 2)
copy(clone, original[1:3])
clone[0] = 99
fmt.Println(original) // [1 2 3 4 5] — unchanged`copy`: Copying Elements Between Slices
src := []int{1, 2, 3, 4, 5}
dst := make([]int, 3)
n := copy(dst, src) // copies min(len(dst), len(src)) elements
fmt.Println(n, dst) // 3 [1 2 3]
// copy also works with strings to []byte
bs := make([]byte, 5)
n = copy(bs, "hello")
fmt.Println(n, bs) // 5 [104 101 108 108 111]Removing Elements from a Slice
Go has no delete function for slices. The standard pattern uses append:
s := []int{1, 2, 3, 4, 5}
i := 2 // index to remove
// Remove element at index i (without preserving order)
s[i] = s[len(s)-1]
s = s[:len(s)-1]
// [1 2 5 4]
// Remove element at index i (preserving order)
s = append(s[:i], s[i+1:]...)
// Caution: this shares memory with the original if there are other sub-slicesCommon Slice Patterns
// Stack with a slice
stack := []int{}
stack = append(stack, 1, 2, 3) // push
top := stack[len(stack)-1] // peek
stack = stack[:len(stack)-1] // pop
// Filter elements
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var evens []int
for _, n := range nums {
if n%2 == 0 {
evens = append(evens, n)
}
}Maps: Typed Hash Tables
A map is an unordered collection of key-value pairs with static types for both:
// Create a map
m1 := map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
m2 := make(map[string]int) // empty map, ready to use
var m3 map[string]int // nil map — cannot write to it
// Basic operations
m2["key"] = 42 // write
value := m2["key"] // read (zero value if not present)
delete(m2, "key") // remove
fmt.Println(len(m2)) // number of pairsThe "Comma Ok" Idiom
When reading from a map, always use the "comma ok" idiom to distinguish between "key does not exist" and "key exists with zero value":
scores := map[string]int{"Ana": 95, "Luis": 0}
// Without comma ok — cannot distinguish
a := scores["Ana"] // 95
p := scores["Pedro"] // 0 — zero value or actually 0?
// With comma ok — correct form
if val, ok := scores["Luis"]; ok {
fmt.Printf("Luis exists with value %d\n", val) // Luis exists with value 0
}
if _, ok := scores["Pedro"]; !ok {
fmt.Println("Pedro is not in the map")
}Iterating Over a Map
m := map[string]int{"a": 1, "b": 2, "c": 3}
// Iteration order is NOT guaranteed
for key, value := range m {
fmt.Printf("%s: %d\n", key, value)
}
// Keys only
for key := range m {
fmt.Println(key)
}
// Sorted iteration: extract keys, sort, iterate
import "sort"
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("%s: %d\n", k, m[k])
}Maps with Struct Values
type Student struct {
Name string
Grade float64
}
students := map[string]Student{
"A001": {Name: "Ana", Grade: 9.5},
"A002": {Name: "Luis", Grade: 8.7},
}
// To modify a field, you must replace the entire struct
student := students["A001"]
student.Grade = 10.0
students["A001"] = student
// Alternative: use *Student as value
studentPtrs := map[string]*Student{
"A001": {Name: "Ana", Grade: 9.5},
}
studentPtrs["A001"].Grade = 10.0 // direct modificationnil vs. Empty: The Important Distinction
// Nil slice
var sNil []int
fmt.Println(sNil == nil) // true
fmt.Println(len(sNil)) // 0 — safe
sNil = append(sNil, 1) // safe — append handles nil
// Empty slice
sEmpty := []int{}
fmt.Println(sEmpty == nil) // false
fmt.Println(len(sEmpty)) // 0
// JSON: nil → null, empty → []
// Nil map
var mNil map[string]int
fmt.Println(mNil == nil) // true
fmt.Println(len(mNil)) // 0 — safe
val := mNil["key"] // 0 — safe
// mNil["key"] = 1 // PANIC! cannot write to nil map
// Empty map
mEmpty := make(map[string]int) // or map[string]int{}
mEmpty["key"] = 1 // safeWith slices and maps mastered, in the next lesson we will explore pointers — how Go manages memory without the dangerous arithmetic of C.
Sign in to track your progress