Skip to main content
Go Slice Internals

Arrays, Slices, and Maps

Go provides flexible composite types for grouping data.

Arrays

An array is a fixed-size sequence of elements of a specific type.
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)

primes := [6]int{2, 3, 5, 7, 11, 13}
Arrays cannot be resized. This limits their use cases, but they form the foundation for slices.

Slices

Slices are dynamically-sized, flexible views into the elements of an array. In practice, slices are much more common than arrays.
primes := [6]int{2, 3, 5, 7, 11, 13}

var s []int = primes[1:4] // [3, 5, 7]

Slice Internals

A slice does not store any data itself. It just describes a section of an underlying array. Understanding this is crucial for writing efficient Go code. Go Slice Internals A slice has three components:
  1. Pointer: Points to the first element of the slice in the underlying array.
  2. Length: The number of elements currently in the slice (accessible via len()).
  3. Capacity: The total number of elements in the underlying array from the slice’s starting point (accessible via cap()).
Why This Matters: When you slice an array or another slice, you’re creating a new slice header that points to the same underlying array. Changes to elements in one slice affect the other.
primes := [6]int{2, 3, 5, 7, 11, 13}
s1 := primes[1:4] // [3, 5, 7]
s2 := primes[2:5] // [5, 7, 11]

s1[1] = 99 // Modifies the underlying array
fmt.Println(s2[0]) // Prints 99, not 5!

Creating Slices

You can create slices using make, which allocates a new underlying array:
a := make([]int, 5)     // len=5, cap=5, [0 0 0 0 0]
b := make([]int, 0, 5)  // len=0, cap=5, []
c := []int{1, 2, 3}     // len=3, cap=3, [1 2 3]
The difference between length and capacity allows slices to grow efficiently without reallocating on every append.

Appending to Slices

The built-in append function adds elements to a slice. Here’s what happens under the hood:
  1. If len < cap: The element is added to the existing array, and length is incremented.
  2. If len == cap: A new, larger array is allocated (typically 2x the current capacity), existing elements are copied, and the new element is added.
var s []int              // len=0, cap=0
s = append(s, 0)         // len=1, cap=1 (new array allocated)
s = append(s, 1)         // len=2, cap=2 (new array allocated)
s = append(s, 2, 3, 4)   // len=5, cap=6 (new array allocated, grew to 4, then to 6)
Important: append returns a new slice value. You must assign it back:
s = append(s, 5) // Correct
append(s, 5)     // Wrong! The result is discarded

Range

The range form of the for loop iterates over a slice or map.
pow := []int{1, 2, 4, 8, 16}

for i, v := range pow {
    fmt.Printf("2**%d = %d\n", i, v)
}
You can skip the index or value by assigning to _.
for _, value := range pow {
    fmt.Println(value)
}

Maps

A map maps keys to values. The zero value of a map is nil.
type Vertex struct {
    Lat, Long float64
}

var m map[string]Vertex

func main() {
    m = make(map[string]Vertex)
    m["Bell Labs"] = Vertex{
        40.68433, -74.39967,
    }
    fmt.Println(m["Bell Labs"])
}

Map Literals

var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google":    {37.42202, -122.08408},
}

Mutating Maps

m := make(map[string]int)

m["Answer"] = 42
fmt.Println("The value:", m["Answer"])

delete(m, "Answer")
fmt.Println("The value:", m["Answer"])

// Check if key exists
v, ok := m["Answer"]
fmt.Println("The value:", v, "Present?", ok)