Skip to content

Go Arrays and Slices — Fixed Arrays append make and copy Explained

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Go Arrays and Slices. We cover key concepts, practical examples, and best practices to help you master this topic.

Go arrays have fixed size determined at compile time while slices are dynamically-sized flexible views backed by arrays, with append for growth, make for allocation, and copy for duplication.

What You'll Learn

  • Fixed-size arrays and their limitations
  • Slice creation, growth, and manipulation
  • append, make, and copy functions
  • Slice internals: pointer, length, capacity

Why It Matters

Slices are Go's most important collection type. Docker uses slices for container lists and configuration. Kubernetes uses slices for pod collections and API responses. DodaZIP manages file lists with slices. Mastering slices is essential for effective Go programming.

Real-World Use

An API handler decodes a JSON array into a slice of structs. A data processor accumulates results in a slice. A log parser stores lines in a slice. Slices are everywhere in Go.

flowchart LR
    A["Arrays & Slices"] --> B["Arrays"]
    B --> C["Slices"]
    C --> D["append"]
    D --> E["make & copy"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b

Arrays

Arrays have a fixed size that's part of their type:

var a [5]int          // [0, 0, 0, 0, 0]
b := [3]int{1, 2, 3}  // [1, 2, 3]
c := [...]int{1, 2, 3, 4}  // Compiler counts: [4]int

fmt.Println(a[0])     // 0
fmt.Println(len(b))   // 3
a[0] = 10
fmt.Println(a)        // [10, 0, 0, 0, 0]

Arrays are values — assigning or passing copies the entire array.

Slices

Slices are dynamically-sized, flexible views into arrays:

// Create slice from array
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4]      // [2, 3, 4]

// Create slice directly
s := []int{1, 2, 3}    // Length 3, capacity 3
var s []int            // nil slice (zero value)

Slice Internals

A slice is a struct of three fields: pointer to underlying array, length, and capacity.

s := []int{10, 20, 30, 40, 50}
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
// len=5 cap=5 [10 20 30 40 50]

s = s[:3]
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
// len=3 cap=5 [10 20 30]

s = s[:cap(s)]
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
// len=5 cap=5 [10 20 30 40 50]

Append

append grows a slice:

var s []int
s = append(s, 1)       // [1]
s = append(s, 2, 3)    // [1, 2, 3]
s = append(s, []int{4, 5}...)  // [1, 2, 3, 4, 5]

fmt.Println(s)         // [1 2 3 4 5]

If the backing array is full, append allocates a new, larger array and copies elements over.

Make

make allocates a slice with specified length and capacity:

s := make([]int, 5)        // len=5, cap=5 [0 0 0 0 0]
s := make([]int, 3, 10)    // len=3, cap=10 [0 0 0]

// Pre-allocating for performance
s := make([]int, 0, 100)   // Empty but with capacity

Copy

copy copies elements between slices:

src := []int{1, 2, 3}
dst := make([]int, len(src))
n := copy(dst, src)       // Returns number of copied elements

fmt.Println(dst)          // [1 2 3]
fmt.Println(n)            // 3

// Copy fewer than source
dst2 := make([]int, 2)
copy(dst2, src)           // Copies only first 2
fmt.Println(dst2)         // [1 2]

Slice Operations

s := []int{1, 2, 3, 4, 5}

// Append to front (expensive)
s = append([]int{0}, s...)

// Insert at position
s = append(s[:2], append([]int{99}, s[2:]...)...)

// Remove by index
s = append(s[:2], s[3:]...)

// Pop from end
last := s[len(s)-1]
s = s[:len(s)-1]

// Pop from front
first := s[0]
s = s[1:]

Iterating Over Slices

s := []string{"apple", "banana", "cherry"}

// With index and value
for i, v := range s {
    fmt.Printf("%d: %s\n", i, v)
}

// Values only
for _, v := range s {
    fmt.Println(v)
}

// Indexes only
for i := range s {
    fmt.Println(i)
}

Multi-Dimensional Slices

// 2D slice
matrix := make([][]int, 3)
for i := range matrix {
    matrix[i] = make([]int, 3)
}

// Matrix operations
matrix[0][0] = 1
matrix[1][1] = 1
matrix[2][2] = 1
fmt.Println(matrix)  // [[1 0 0] [0 1 0] [0 0 1]]

Common Mistakes

1. Append Without Assigning

s := []int{1, 2, 3}
append(s, 4)           // s is unchanged!
s = append(s, 4)       // Correct

2. Confusing Array and Slice Types

a := [3]int{1, 2, 3}  // Array (size is part of type)
s := []int{1, 2, 3}   // Slice

// Can't use array where slice is expected
// func handle(s []int) { }
// handle(a)  // Error
handle(a[:])  // Convert array to slice

3. Slicing Past Capacity

s := make([]int, 3, 5)
// s[:6]  // Panic: slice bounds out of range
s = s[:cap(s)]  // OK — up to capacity

4. Modifying Underlying Array Through Multiple Slices

s1 := []int{1, 2, 3, 4, 5}
s2 := s1[1:3]
s2[0] = 99
fmt.Println(s1[1])  // 99 — s1 modified too!

5. Nil vs Empty Slice

var s1 []int         // nil slice, len=0, cap=0
s2 := []int{}        // empty slice, len=0, cap=0

fmt.Println(s1 == nil)  // true
fmt.Println(s2 == nil)  // false

// Both work with append and range

Practice Questions

1. What is the difference between arrays and slices in Go?

Arrays have a fixed size determined at compile time ([5]int). Slices are dynamically-sized ([]int) and backed by an array. Arrays are value types; slices are reference types.

2. How does append work?

append adds elements to the end of a slice. If the backing array has capacity, it writes there. If not, it allocates a new, larger array and copies elements over.

3. What does make do for slices?

make([]int, length, capacity) creates a slice with specified length and capacity, initializing elements to zero values. Capacity is optional — defaults to length.

4. Why is pre-allocating capacity with make important?

Pre-allocating avoids repeated allocations as the slice grows. Starting with make([]int, 0, 1000) for a slice that will hold ~1000 elements avoids multiple allocations.

Challenge: Write a function that removes duplicate elements from a slice while preserving order.

Solution
package main

import "fmt"

func unique(s []int) []int {
    seen := make(map[int]bool)
    result := make([]int, 0, len(s))
    for _, v := range s {
        if !seen[v] {
            seen[v] = true
            result = append(result, v)
        }
    }
    return result
}

func main() {
    nums := []int{1, 2, 2, 3, 4, 3, 5, 1}
    fmt.Println(unique(nums))  // [1 2 3 4 5]
}

FAQ

{{< faq question="When should I use an array instead of a slice?" >}} Rarely. Use arrays when you need a fixed-size collection that's part of the type signature (e.g., [16]byte for cryptographic hashes). For almost everything else, use slices. {{< /faq >}}

{{< faq question="How does slice capacity grow?" >}} Go doubles the capacity when appending beyond the current capacity (for small slices). For larger slices, the growth factor is 1.25. This amortizes the cost of reallocation. {{< /faq >}}

{{< faq question="Can I convert a slice to an array pointer?" >}} Yes, in Go 1.17+: arr := (*[3]int)(s). The slice must have at least as many elements as the array. {{< /faq >}}

{{< faq question="What is the zero value of a slice?" >} nil. A nil slice has length 0 and capacity 0. You can append to a nil slice. Range over a nil slice yields no iterations. {{< /faq >}}

{{< faq question="How do I sort a slice?" >} Use the sort package: sort.Ints(s), sort.Strings(s), or sort.Slice(s, func(i, j int) bool { return s[i] < s[j] }). {{< /faq >}}

Try It Yourself

package main

import "fmt"

func main() {
    // Create and manipulate slices
    s := make([]int, 0, 10)
    for i := 0; i < 10; i++ {
        s = append(s, i*i)
        fmt.Printf("len=%d cap=%d\n", len(s), cap(s))
    }
    fmt.Println("Squares:", s)

    // Sub-slice
    first5 := s[:5]
    last5 := s[5:]
    fmt.Println("First 5:", first5)
    fmt.Println("Last 5:", last5)

    // Copy
    copy_s := make([]int, len(s))
    copy(copy_s, s)
    fmt.Println("Copy:", copy_s)

    // Slice tricks
    // Remove element at index 3
    s = append(s[:3], s[4:]...)
    fmt.Println("After removing index 3:", s)
}

What's Next

Now that you understand arrays and slices, learn about maps and structs for key-value data and Composite types.

Topic Description Link
Go Maps & Structs map, struct, tags, zero values {{< ref "08-maps-structs" >}}
Go Pointers &, *, nil, new {{< ref "09-pointers" >}}
Rust Collections Compare Rust Vec and HashMap Rust

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse Go