arrays-vs-slices-diferencias-longitud-capacidad

Arrays and Slices in Go: Differences, Memory, len and cap

  • 5 min

Arrays and slices in Go are two ways to store sequences of values of the same type, but with a huge difference: arrays have a fixed size, and slices are flexible views over an array.

If you come from JavaScript or Python, you are used to having a dynamic list where you put things and it grows on its own. If you come from C, you know static arrays and pointers.

In Go, we have two types of sequential collections that, although declared similarly, have different semantics: arrays and slices.

Understanding this difference prevents errors when copying or sharing data. Let’s open Go’s hood to see how they relate to memory and why we will normally use slices.

Arrays: Fixed Size and Value Semantics

An array in Go is a sequence of elements of a fixed size, determined at compile time.

The defining characteristic of an array is that its length is part of its type.

  • [5]int is an array of 5 integers.
  • [10]int is an array of 10 integers.
  • They are different types. You cannot assign one to the other.

Declaration

var numbers [5]int // Array of 5 integers initialized to 0
numbers[0] = 10
numbers[4] = 20

// Declaration and literal initialization
colors := [3]string{"Red", "Green", "Blue"}

// Let the compiler count for us (...)
primes := [...]int{2, 3, 5, 7, 11} // This is an array of size 5
Copied!

Value Behavior

Unlike C (where an array is a pointer to the first element) or Java (where it is a reference), in Go arrays are VALUES.

If you assign an array to another variable, or pass it as an argument to a function, a complete copy of all its data is made.

a := [3]int{1, 2, 3}
b := a // The entire array 'a' is COPIED into 'b'

b[0] = 99

fmt.Println(a) // Prints [1 2 3] - 'a' has not changed
fmt.Println(b) // Prints [99 2 3]
Copied!

Passing large arrays like [1000000]int to functions has copy semantics and can be costly. If we want to share data without copying it, we normally pass a slice or a pointer to the array.

Slices: Flexible Windows

A slice is a view built over an underlying array. Its length can change through operations like append, and it is the type we will commonly use for sequences.

Syntax: It is the same as an array, but without a number inside the brackets. var mySlice []int

What a Slice Contains

As a mental model, we can think of a slice as a small descriptor with three pieces of data:

  1. Reference: Points to the first accessible element within an underlying array.
  2. Length (len): How many elements the slice contains.
  3. Capacity (cap): How many elements the slice could contain without needing to allocate more memory (i.e., the size of the underlying array from the pointer).

Creating Slices

We can create slices in several ways:

// 1. Literal (Looks like an array, but without a number in [])
names := []string{"Alice", "Bob", "Charlie"}

// 2. From an existing array (Slice operation)
baseArray := [5]int{10, 20, 30, 40, 50}
mySlice := baseArray[1:4] // From index 1 up to (4-1) -> {20, 30, 40}

// 3. Using the make() function
// make([]Type, length, capacity)
buffer := make([]int, 5, 10)
Copied!

Header Copy, Shared Data

Since a slice is just a “boosted pointer”, when you copy a slice or pass it to a function, you only copy the small header. Both slices still point to the same underlying array.

sliceA := []int{1, 2, 3}
sliceB := sliceA // We copy the descriptor (reference, len, cap)

sliceB[0] = 99

fmt.Println(sliceA) // Prints [99 2 3] -> 'sliceA' HAS CHANGED!
Copied!

Difference Between Length (len) and Capacity (cap)

This distinction is fundamental to understanding performance in Go.

  • Length (len): It is the number of elements the slice currently “sees”. These are the elements you can access (slice[0] through slice[len-1]).
  • Capacity (cap): It is the actual space available in the array behind the scenes.

We can imagine a 500ml glass (cap) that currently contains 200ml (len).

func main() {
    // Create slice: length 2, capacity 5
    s := make([]int, 2, 5)
    s[0] = 10
    s[1] = 20

    fmt.Printf("Len: %d, Cap: %d, Value: %v\n", len(s), cap(s), s)
    // Output: Len: 2, Cap: 5, Value: [10 20]

    // fmt.Println(s[3]) // ERROR: panic, index out of range
    // Even though there is capacity (memory allocated), the slice only "sees" up to len.
}
Copied!

Extending a View with Reslicing

Since we have spare capacity (the glass is not full), we can extend the slice to “see” more elements that are already in memory but were hidden.

// Extend the slice one more position to the right
s = s[:3]
fmt.Printf("Len: %d, Cap: %d\n", len(s), cap(s))
// Now len is 3. The value of s[2] will be 0 (zero value) because it was already in memory.
Copied!

Slice Operator [low:high]

The slice operator allows us to create a new slice pointing to a segment of an array or another slice.

Syntax: source[low : high]

  • Includes index low.
  • Excludes index high.
arr := [5]int{0, 1, 2, 3, 4}

s1 := arr[1:4] // Indices 1, 2, 3 -> Value: [1 2 3]
s2 := arr[:2]  // Indices 0, 1    -> Value: [0 1] (default low is 0)
s3 := arr[2:]  // Indices 2..end  -> Value: [2 3 4] (default high is len)
Copied!

Beware of memory: If you have a huge 1GB array in memory and create a small slice that points to only 3 elements of that array, the entire 1GB array remains in memory because the slice is referencing it. The Garbage Collector cannot release it.