Web Analytics

Arrays & Slices

Beginner ~40 min read

Arrays and slices are fundamental data structures in Go. While arrays have a fixed size, slices are dynamic and flexibleโ€”making them the most commonly used collection type. In this lesson, you'll master both and learn when to use each.

Arrays

An array is a fixed-size sequence of elements of the same type:

Output
Click Run to execute your code
Array Characteristics:
  • Fixed size - Size is part of the type
  • Value type - Copying creates a new array
  • Zero-indexed - First element is at index 0
  • Initialized to zero values by default

Array Declaration

// Declare with size
var arr [5]int  // [0 0 0 0 0]

// Declare and initialize
numbers := [5]int{1, 2, 3, 4, 5}

// Let compiler count the size
numbers := [...]int{1, 2, 3, 4, 5}  // Size is 5

// Initialize specific indices
arr := [5]int{0: 10, 2: 20, 4: 30}  // [10 0 20 0 30]
Important: Arrays with different sizes are different types! [3]int and [5]int are completely different types.

Slices

Slices are dynamic, flexible views into arrays. They're the most common way to work with sequences in Go:

Output
Click Run to execute your code
Slice Characteristics:
  • Dynamic size - Can grow and shrink
  • Reference type - Points to underlying array
  • Three components - Pointer, length, capacity
  • Most commonly used collection in Go

Creating Slices

// Slice literal
numbers := []int{1, 2, 3, 4, 5}

// Using make (length and capacity)
slice := make([]int, 5)      // length 5, capacity 5
slice := make([]int, 5, 10)  // length 5, capacity 10

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

// Empty slice
var slice []int  // nil slice

Length and Capacity

Slices have both length and capacity:

Property Function Description
Length len(slice) Number of elements in the slice
Capacity cap(slice) Number of elements in underlying array (from first element)
slice := make([]int, 5, 10)
fmt.Println(len(slice))  // 5 (length)
fmt.Println(cap(slice))  // 10 (capacity)

// Slicing affects length but not capacity
s := []int{1, 2, 3, 4, 5}
s2 := s[1:3]  // [2 3]
fmt.Println(len(s2))  // 2
fmt.Println(cap(s2))  // 4 (from index 1 to end of array)

Appending to Slices

The append function adds elements to a slice:

Output
Click Run to execute your code
How Append Works:
  1. If capacity is sufficient, append adds to existing array
  2. If capacity is exceeded, a new larger array is allocated
  3. Elements are copied to the new array
  4. The slice is updated to point to the new array

Append Patterns

// Append single element
slice = append(slice, 6)

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

// Append another slice (use ...)
slice1 := []int{1, 2, 3}
slice2 := []int{4, 5, 6}
slice1 = append(slice1, slice2...)  // [1 2 3 4 5 6]
Best Practice: Always assign the result of append back to the slice: slice = append(slice, value). The underlying array may change!

Slice Operations

Slicing Syntax

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

s[1:4]   // [1 2 3] - from index 1 to 3
s[:3]    // [0 1 2] - from start to index 2
s[3:]    // [3 4 5] - from index 3 to end
s[:]     // [0 1 2 3 4 5] - entire slice

// With capacity
s[1:3:5]  // [1 2] with capacity 4

Copy Function

Output
Click Run to execute your code
Pro Tip: copy returns the number of elements copied, which is the minimum of len(dst) and len(src).

Arrays vs Slices: When to Use Which?

Feature Array Slice
Size Fixed Dynamic
Type Value type Reference type
Passing to functions Copies entire array Copies slice header (cheap)
Common usage Rare (fixed-size data) Very common
Can grow? No Yes (with append)
Best Practice: Use slices in almost all cases. Arrays are mainly used when you need a fixed size or for performance-critical code where you want to avoid heap allocations.
Arrays vs Slices in Go - Visual Comparison

Figure: Arrays vs Slices - Understanding fixed-size arrays and dynamic slices with their internal structure

Common Mistakes

1. Not assigning append result

// โŒ Wrong - append result not assigned
slice := []int{1, 2, 3}
append(slice, 4)  // Doesn't modify slice!
fmt.Println(slice)  // [1 2 3]

// โœ… Correct
slice = append(slice, 4)
fmt.Println(slice)  // [1 2 3 4]

2. Slice sharing underlying array

// โŒ Unexpected - slices share array
original := []int{1, 2, 3, 4, 5}
slice1 := original[0:3]
slice2 := original[2:5]
slice1[2] = 99
fmt.Println(slice2)  // [99 4 5] - affected!

// โœ… Correct - use copy for independence
original := []int{1, 2, 3, 4, 5}
slice1 := make([]int, 3)
copy(slice1, original[0:3])
slice1[2] = 99
fmt.Println(original)  // [1 2 3 4 5] - unchanged

3. Comparing slices with ==

// โŒ Wrong - can't compare slices
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
if s1 == s2 {  // Error: invalid operation
    // ...
}

// โœ… Correct - compare manually or use reflect.DeepEqual
func equal(a, b []int) bool {
    if len(a) != len(b) {
        return false
    }
    for i := range a {
        if a[i] != b[i] {
            return false
        }
    }
    return true
}

Exercise: Slice Manipulation

Task: Create functions to manipulate slices.

Requirements:

  • Create a function filter that removes even numbers
  • Create a function reverse that reverses a slice
  • Test with slice: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  • Print original, filtered, and reversed
Show Solution
package main

import "fmt"

// filter removes even numbers from slice
func filter(numbers []int) []int {
    result := []int{}
    for _, num := range numbers {
        if num%2 != 0 {  // Keep odd numbers
            result = append(result, num)
        }
    }
    return result
}

// reverse reverses a slice
func reverse(numbers []int) []int {
    result := make([]int, len(numbers))
    for i, num := range numbers {
        result[len(numbers)-1-i] = num
    }
    return result
}

// reverseInPlace reverses a slice in place
func reverseInPlace(numbers []int) {
    for i := 0; i < len(numbers)/2; i++ {
        j := len(numbers) - 1 - i
        numbers[i], numbers[j] = numbers[j], numbers[i]
    }
}

func main() {
    original := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    
    fmt.Println("Original:", original)
    
    // Filter even numbers
    filtered := filter(original)
    fmt.Println("Filtered (odd only):", filtered)
    
    // Reverse
    reversed := reverse(original)
    fmt.Println("Reversed:", reversed)
    
    // Bonus: Reverse in place
    nums := []int{1, 2, 3, 4, 5}
    reverseInPlace(nums)
    fmt.Println("Reversed in place:", nums)
}

Summary

  • Arrays have fixed size and are value types
  • Slices are dynamic and reference underlying arrays
  • Slices have length and capacity - use len() and cap()
  • append adds elements, may allocate new array
  • copy copies elements between slices
  • Slicing syntax: s[low:high] or s[low:high:max]
  • Always assign append result back to the slice
  • Use slices for almost everything (not arrays)

What's Next?

Now that you understand slices, you're ready to learn about Mapsโ€”Go's built-in hash table type for key-value pairs. Maps are essential for many programming tasks!