Go

Go Arrays and Slices: A Deep Dive into Performance and Flexibility

Learn the differences between arrays and slices in Go, how slices work under the hood with pointer, length and capacity, and how to use them effectively for maximum performance.

Aleksandr Perederei 2026-02-01 10 min

Arrays

An array is a data structure that stores a sequence of elements of a single type with a fixed length. Arrays are the foundational data structure behind slices and maps. Once you understand arrays, it becomes much easier to grasp how slices and maps work under the hood.

In Go, we can define an array like this:

var x [5]int

In this example, we created an array of five elements. Each element is initialized with its zero value (which is 0 for integers).

We can also create an array with specific values:

x := [5]int{1, 200, -300, 400, 500}

It isn’t necessary to specify the size of the array explicitly. You can replace the number of elements with ... and the compiler will automatically calculate the length:

v := [...]int{1, 200, -300, 400, 500}

Go also allows us to create two-dimensional and multidimensional arrays:

// Create a 2-dimensional array of strings
var xx [2][2]string

// Predefined 2D array
xx := [2][2]string{{"00", "01"}, {"10", "11"}}

Iteration

As in other languages, we can iterate over arrays in two ways:

  • Using a for loop with the len function
  • Using a for loop with the range keyword
// main.go
package main

import "fmt"

func main() {
    x := [3]string{"I", "love", "Go"}

    // Using a for loop with len
    for i := 0; i < len(x); i++ {
        fmt.Println(i, x[i])
    }

    // Using for-range
    for index, value := range x {
        fmt.Println(index, value)
    }
}

Try it yourself: Copy the code into your IDE and run it. What is the result? Can you change the output to print I love Go on a single line?

Question

What will be the result of this small program?

// quiz.go
package main

import "fmt"

func main() {
    var quizArray [10]string
    for i, v := range quizArray {
        fmt.Println(i, v)
    }
}

Hint: Think about what the zero value of a string is in Go. The loop will print indices 0-9, each with an empty string.

Slices

Let’s try to figure out the output of the following piece of code. Take a careful look:

// slices_quiz.go
package main

import "fmt"

func main() {
    x := []int{1, 2, 3, 0, -1}
    newX := x[0:2]
    newX = append(newX, 1)
    x[0] = 3
    fmt.Println(x)
    fmt.Println(newX)
    // What is the output?
}

Before you continue… Spend 1-2 minutes thinking about the result before reading on.

Surprised? The correct answer is:

x    = [3 2 1 0 -1]
newX = [3 2 1]

We’ll explain why in the following paragraphs, as we explore how slices work under the hood.

Structure of Slices

A slice is a data structure that provides a dynamic, resizable view into the elements of an array. We can grow and shrink slices, append elements to them efficiently, and create sub-slices that reference portions of the original data.

Under the hood, a slice consists of three components:

  • Address (pointer) — a pointer to the first element of the underlying array that the slice can access.
  • Length — the number of elements the slice currently contains (accessible via len()).
  • Capacity — the total number of elements available for growth before a new underlying array must be allocated (accessible via cap()).

Creating a Slice

In Go, you can create a slice in three ways:

  • Using a slice literal (:= operator)
  • Using the built-in make function
  • Declaring without initialization (creating a nil slice)

Creating a slice with make

The make function has the following signature:

make([]T, length, capacity)

The first argument is the type. The second argument specifies the length. An optional third argument specifies the capacity, which must be no smaller than the length.

// A slice of int with length and capacity of 2
x := make([]int, 2)

// A slice of float64 with length 3 and capacity 5
x := make([]float64, 3, 5)

// Error: length cannot be greater than capacity
x := make([]string, 10, 2)

Creating a slice with a literal

// A slice with length and capacity of 3
x := []int{1, 2, 3}

// A nil slice of integers
var x []int

Key difference: When you specify a value inside the brackets — e.g. [5]int — you create an array. When the brackets are empty — []int — you create a slice.

Working with Slices

Assigning a Value

Assigning a value to a slice element is straightforward:

// assign.go
package main

import "fmt"

func main() {
    x := []string{"I", "love", "you"}
    x[2] = "Go"
    fmt.Println(x)
    // Output: [I love Go]
}

Slicing

Let’s create a slice of strings with four elements and then take a sub-slice:

isLove := []string{"I", "love", "you", "?"}

// Create a new slice containing "love"
trueLove := isLove[1:2]

Important! When you create a sub-slice, you get a new slice header, but it shares the same underlying array as the original. Modifying elements in one will affect the other.

How are the length and capacity of a sub-slice calculated?

If the original slice has a capacity of c, then for a sub-slice [i:k]:

  • length = k - i
  • capacity = c - i

Append

The append function adds elements to the end of a slice and returns the updated slice. You must store the result — often back into the same variable:

x := []float64{1, 2, 3, 4, 5}
x = append(x, 6)

// Append multiple elements
x = append(x, 7, 8, 9)

// Append another slice using the spread operator
newX := []float64{6, 7, 8}
x = append(x, newX...)

Copy

Sometimes you need an independent copy of a slice. Go provides the built-in copy function for this purpose. It copies elements from a source slice into a destination slice and returns the number of elements copied (the minimum of the two lengths).

// copy.go
package main

import "fmt"

func main() {
    src := []int{1, 2, 3, 4, 5}
    dst := make([]int, 3)

    // Copies the first 3 elements from src to dst
    copy(dst, src)

    fmt.Println(dst)
    // Output: [1 2 3]
}

Iteration

Go provides two standard ways to iterate over a slice (the same as for arrays):

// iteration.go
package main

import "fmt"

func main() {
    r := []int{1, 2, 3}

    // Using for-range with index and value
    for i, v := range r {
        fmt.Println(i, v)
    }

    // Skip the index with a blank identifier
    for _, v := range r {
        fmt.Println(v)
    }

    // Use only the index
    for i := range r {
        fmt.Println(i)
    }
}

Note: When iterating with range, the value is a copy of the element — it’s placed at a new memory address on each iteration. The range operator always starts from the beginning of the slice. If you need to start from a different position, use a traditional for loop:

package main

import "fmt"

func main() {
    r := []int{1, 2, 3}
    for i := 0; i < len(r); i++ {
        fmt.Println(i, r[i])
    }
}

External Resources

References

Get engineering articles in your inbox

Practical advice on system design, technical leadership, and career growth. No spam.

Book Your Growth Session

Let's identify your #1 skill gap and create a 90-day learning plan to level up your engineering abilities.

Powered by Cal.com - No account required