Go: Go Arrays and Slices

Slices are at the heart of how Go handles collection data—they are lightweight "views" of arrays, and their ability to resize dynamically makes collection operations both fast and flexible.

Arrays are rarely used directly in Go (because their length is fixed), but slices are everywhere. To understand slices, you must first understand arrays—at their core, a slice is a pointer to an مصفوفة plus its length and capacity. In this lesson, you'll master all the core concepts of Go's collection types.

1. You will learn



2. A True Story of a Data Acquisition Engineer

(1) Pain Point: A fixed-size مصفوفة cannot accommodate dynamic data

Alice is a data acquisition engineer. She recently needed to write a web crawler:

"I need to retrieve 10,000 records from each of 5 data sources and store them in memory to remove duplicates. At first, I used Python lists—just a simple 'append' and I was done. After switching to Go, I used a fixed-length مصفوفة like [10000]int, but it panicked on the very first record: 'index out of range'."

She opened the Go code she had written:

GO
// Version 1: Fixed-length array; cannot grow
var records [10000]int
for i := 0; i < 50000; i++ {
    records[i] = i  // ❌ i >= 10000 causes panic!
}

The boss said, "The number of data sources changes every day, so your fixed-length array won't work." Only then did Alice realize that Go arrays are value types, and the length is part of the type—[5]int and [10]int are different types.

(2) Solution in Go: Using slices

GO
// data_collector.go
package main

import "fmt"

func main() {
    // Slices: Dynamic length, automatic resizing
    var records []int  // nil slice, can append

    // Simulate retrieving 10,000 records from each of 5 data sources
    for source := 1; source <= 5; source++ {
        for i := 0; i < 10000; i++ {
            value := source*10000 + i
            records = append(records, value)  // auto-resize
        }
    }

    fmt.Printf("Total number of records: %d\n", len(records))
    fmt.Printf("Underlying capacity: %d\n", cap(records))
    fmt.Printf("Top 3: %v\n", records[:3])
    fmt.Printf("Last 3: %v\n", records[len(records)-3:])
}

Output:

TEXT 📖 Display only
Total number of records: 50000
Underlying capacity: 65536
Top 3: [10000 10001 10002]
Last 3: [59997 59998 59999]

(3) Performance: مصفوفة vs. slice

Dimension مصفوفة [n]T slice []T
Length Fixed at compile time Variable at runtime
Type [5]int[10]int []int Generic
Parameter Passing Value Passing (copies the entire مصفوفة) Reference Passing (only 24 bytes)
Resize Not supported append automatic
Use Case Fixed Size (e.g., مخزن مؤقت) 99% of Cases
💡 Tip: In Go, slices are used in 99% of "collection" scenarios; arrays are used only when the size is fixed (such as the 32-byte مخزن مؤقت for SHA-256) or when value semantics are required.



3. مصفوفة

(1) Defining and Initializing Arrays

GO
package main

import "fmt"

func main() {
    // Method 1: Declaration + Initialization
    var a1 [5]int = [5]int{1, 2, 3, 4, 5}

    // Method 2: Type Inference
    a2 := [5]int{1, 2, 3, 4, 5}

    // Method 3: Partial Initialization (with the Rest Set to Zero)
    a3 := [5]int{1, 2}  // [1 2 0 0 0]

    // Method 4: Use ... to let the compiler infer the length
    a4 := [...]int{1, 2, 3, 4}  // length 4

    // Method 5: Specifying Index Initialization
    a5 := [5]int{0: 10, 4: 50}  // [10 0 0 0 50]

    fmt.Println(a1, a2, a3, a4, a5)
}

Output:

TEXT 📖 Display only
[1 2 3 4 5] [1 2 3 4 5] [1 2 0 0 0] [1 2 3 4] [10 0 0 0 50]

(2) Iterating Through Arrays

GO
package main

import "fmt"

func main() {
    nums := [4]سلسلة{"Alice", "Bob", "Charlie", "Dave"}

    // Method 1: for حلقة
    for i := 0; i < len(nums); i++ {
        fmt.Printf("%d: %s\n", i, nums[i])
    }

    // Method 2: for range
    for i, name := range nums {
        fmt.Printf("[%d] %s\n", i, name)
    }
}

(3) Arrays are value types

GO
package main

import "fmt"

func modify(arr [3]int) {
    arr[0] = 999  // modifies the copy
    fmt.Printf("Inside function: %v\n", arr)
}

func main() {
    a := [3]int{1, 2, 3}
    modify(a)
    fmt.Printf("Outside function: %v\n", a)  // original array unchanged
}

Output:

TEXT 📖 Display only
Inside function: [999 2 3]
Outside function: [1 2 3]
🔥 Common Mistake: Go arrays are value types, so passing them as arguments copies the entire array. Passing an array with 1 million elements as an argument = copying 1 million elements = a performance disaster. Use slices instead.

▶ Example: Comparing Arrays (Only for Arrays of the Same Type and Length)

GO
package main

import "fmt"

func main() {
    a := [3]int{1, 2, 3}
    b := [3]int{1, 2, 3}
    c := [3]int{1, 2, 4}

    fmt.Println(a == b)  // true
    fmt.Println(a == c)  // false

    // Items of different lengths or types cannot be compared.
    // d := [4]int{1, 2, 3, 4}
    // fmt.Println(a == d)  // ❌ mismatched types
}
▶ Try it Yourself

4. Four Ways to Create Slices

(1) A slice = a "view" of an array

100%
graph TB
    subgraph Slice["Slice (24 bytes)"]
        P["ptr<br/>points to the underlying array"]
        L["len<br/>length"]
        C["cap<br/>capacity"]
    end
    subgraph Array["Underlying array"]
        A0["[0]"]
        A1["[1]"]
        A2["[2]"]
        A3["[3]"]
        A4["[4]"]
        A5["[5]"]
    end
    P --> A1
    P -.-> A2
    P -.-> A3

A slice is a reference to the underlying array—it consists of only 24 bytes (ptr + len + cap). Multiple slices can share the same underlying array.

(2) Creation Method 1: Directly declare a nil slice

GO
var s []int  // nil slice, len=0, cap=0
// You can append, but you cannot access it using `s[0]`.

(3) Creation Method 2: Literal

GO
s := []int{1, 2, 3, 4, 5}  // create and initialize
// Base array = [1 2 3 4 5], len = cap = 5

(4) Creation Method 3: make (specify len and cap)

GO
// Syntax: make([]T, len, cap)
s := make([]int, 5)       // len=5, cap=5, all zero values [0 0 0 0 0]
s := make([]int, 3, 10)   // len=3, cap=10

▶ Example: Comparison of 4 ways to use "make"

GO
package main

import "fmt"

func main() {
    // Method 1: nil slice
    var s1 []int
    fmt.Printf("s1: len=%d cap=%d nil=%v\n", len(s1), cap(s1), s1 == nil)

    // Method 2: Literal
    s2 := []int{1, 2, 3}
    fmt.Printf("s2: len=%d cap=%d %v\n", len(s2), cap(s2), s2)

    // Method 3: Set the length
    s3 := make([]int, 5)
    fmt.Printf("s3: len=%d cap=%d %v\n", len(s3), cap(s3), s3)

    // Method 4: make (length + capacity)
    s4 := make([]int, 3, 10)
    fmt.Printf("s4: len=%d cap=%d %v\n", len(s4), cap(s4), s4)

    // Method 5: Slicing from an array
    arr := [5]int{10, 20, 30, 40, 50}
    s5 := arr[1:4]  // [20 30 40], len=3 cap=4 (4 remaining elements)
    fmt.Printf("s5: len=%d cap=%d %v\n", len(s5), cap(s5), s5)
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
s1: len=0 cap=0 nil=true
s2: len=3 cap=3 [1 2 3]
s3: len=5 cap=5 [0 0 0 0 0]
s4: len=3 cap=10 [0 0 0]
s5: len=3 cap=4 [20 30 40]

(6) Quick Reference: 4 Ways to Create

Method Syntax Use Cases
nil slice var s []T lazy initialization
Literal []T{1, 2, 3} All elements known
make make([]T, len, cap) Pre-allocate capacity
Array Slicing arr[low:high] Subarray View


5. len() and cap()

(1) The Relationship Between len and cap

GO
package main

import "fmt"

func main() {
    s := make([]int, 3, 10)  // len=3, cap=10

    fmt.Printf("Initial: len=%d cap=%d\n", len(s), cap(s))
    // len=3: You can access s[0], s[1], and s[2]
    // cap=10: The underlying array has 10 elements, with room for 7 more to be appended
}

(2) The Meaning of "cap"

cap - len = the number of elements that can still be appended without resizing. If this exceeds cap, append will allocate a new underlying مصفوفة (typically doubling the size).

▶ Example: The Actual Difference Between len and cap

GO
package main

import "fmt"

func main() {
    s := make([]int, 0, 5)  // len=0, cap=5

    for i := 1; i <= 8; i++ {
        s = append(s, i)
        fmt.Printf("append %d: len=%d cap=%d %v\n", i, len(s), cap(s), s)
    }
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
append 1: len=1 cap=5 [1]
append 2: len=2 cap=5 [1 2]
append 3: len=3 cap=5 [1 2 3]
append 4: len=4 cap=5 [1 2 3 4]
append 5: len=5 cap=5 [1 2 3 4 5]
append 6: len=6 cap=10 [1 2 3 4 5 6]  ← cap doubled
append 7: len=7 cap=10 [1 2 3 4 5 6 7]
append 8: len=8 cap=10 [1 2 3 4 5 6 7 8]
💡 Tip: Go's resizing strategy: When len == cap, the new cap is equal to the old cap * 2 (for small slices) or the old cap * 1.25 (for large slices, approximately > 256).



6. Append-based dynamic resizing

(1) Basics of append

GO
package main

import "fmt"

func main() {
    var s []int  // nil

    s = append(s, 1)       // [1]
    s = append(s, 2, 3, 4) // [1 2 3 4]

    // Batch append: Splitting a slice
    s2 := []int{5, 6, 7}
    s = append(s, s2...)   // [1 2 3 4 5 6 7]

    fmt.Println(s)
}

Output:

TEXT 📖 Display only
[1 2 3 4 5 6 7]

▶ Example: Deleting elements using append (no built-in delete دالة)

GO
package main

import "fmt"

// Delete the element at index i
func removeAt(s []int, i int) []int {
    // Concatenation: s[:i] + s[i+1:]
    return append(s[:i], s[i+1:]...)
}

func main() {
    s := []int{1, 2, 3, 4, 5}
    s = removeAt(s, 2)  // delete 3
    fmt.Println(s)       // [1 2 4 5]
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
[1 2 4 5]

(3) The pitfall of append: It may modify the original array

GO
package main

import "fmt"

func main() {
    s := []int{1, 2, 3, 4, 5}
    s2 := s[:3]  // [1 2 3], shares underlying مصفوفة

    s2 = append(s2, 99)  // no resize, modified s's underlying مصفوفة!
    fmt.Println("s:", s)    // [1 2 3 99 5]
    fmt.Println("s2:", s2)  // [1 2 3 99]
}

Output:

TEXT 📖 Display only
s: [1 2 3 99 5]
s2: [1 2 3 99]
🔥 Common Mistake: This is one of Go's most classic pitfalls—slices share the underlying مصفوفة, so calling append may affect other slices. Use copy() or force resizing when strict isolation is required.



7. Slice [low:high:max]

(1) Three types of slice expressions

GO
package main

import "fmt"

func main() {
    arr := [5]int{10, 20, 30, 40, 50}

    // Form 1: [low:high] — Takes [low, high)
    s1 := arr[1:4]
    fmt.Printf("arr[1:4] = %v, len=%d cap=%d\n", s1, len(s1), cap(s1))

    // Format 2: [low:] — From low to the end
    s2 := arr[2:]
    fmt.Printf("arr[2:]  = %v, len=%d cap=%d\n", s2, len(s2), cap(s2))

    // Form 3: [:high] — From the beginning to high
    s3 := arr[:3]
    fmt.Printf("arr[:3]  = %v, len=%d cap=%d\n", s3, len(s3), cap(s3))

    // Format 4: [low:high:max] — Sets a cap (to prevent unexpected additions from affecting the original array)
    s4 := arr[1:3:4]
    fmt.Printf("arr[1:3:4] = %v, len=%d cap=%d\n", s4, len(s4), cap(s4))
}

Output:

TEXT 📖 Display only
arr[1:4] = [20 30 40], len=3 cap=4
arr[2:]  = [30 40 50], len=3 cap=3
arr[:3]  = [10 20 30], len=3 cap=5
arr[1:3:4] = [20 30], len=2 cap=3

(2) The Purpose of [low:high:max]

max sets a limit to prevent append from overwriting other parts of the original array:

GO
package main

import "fmt"

func main() {
    arr := [5]int{10, 20, 30, 40, 50}

    // No limit on max: cap=4; after an append operation, arr[3] will be overwritten
    s1 := arr[1:3]  // [20 30], cap=4
    s1 = append(s1, 999)
    fmt.Println("arr:", arr)  // [10 20 30 999 50] ← overwritten!

    // Limit max=3: cap=2; append creates a new مصفوفة
    s2 := arr[1:3:3]  // [20 30], cap=2
    s2 = append(s2, 888)
    fmt.Println("arr:", arr)  // [10 20 30 999 50] ← unchanged
    fmt.Println("s2:", s2)    // [20 30 888]
}

Output:

TEXT 📖 Display only
arr: [10 20 30 999 50]
arr: [10 20 30 999 50]
s2: [20 30 888]


8. Pitfalls of Sharing the Underlying Array in Slicing (Key Point)

(1) Pitfall 1: Modifying a slice element affects all slices

GO
package main

import "fmt"

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    s1 := arr[0:3]  // [1 2 3]
    s2 := arr[2:5]  // [3 4 5]

    s1[2] = 999  // same underlying array!
    fmt.Println("s1:", s1)  // [1 2 999]
    fmt.Println("s2:", s2)  // [999 4 5]
    fmt.Println("arr:", arr) // [1 2 999 4 5]
}

(2) Pitfall 2: Using append with for range causes an infinite loop

GO
package main

import "fmt"

func main() {
    s := []int{1, 2, 3}
    for _, v := range s {
        s = append(s, v*10)  // dangerous: s is growing
    }
    fmt.Println(s)  // output is غير معرّف
}
🔥 Common Mistake: Never modify the slice itself during a for range loop. Prior to Go 1.22, s in for i, v := range s was a snapshot of the length at the start of the loop; Go 1.22 fixed this in some scenarios, but caution is still advised.

(3) Pitfall 3: Slices passed as function arguments are unexpectedly modified

GO
package main

import "fmt"

// Slicing uses pass-by-reference, so changes made within the function affect the outside.
func addElement(s []int) {
    s = append(s, 100)  // may modify the original array (if no resize)
    fmt.Printf("Inside function: %v\n", s)
}

func main() {
    s := make([]int, 3, 10)
    s[0], s[1], s[2] = 1, 2, 3

    addElement(s)
    fmt.Printf("Outside function: %v\n", s)  // depends on whether resize occurred
}

(4) Use copy() to resolve sharing issues

GO
package main

import "fmt"

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    s1 := arr[0:3]

    // Create a separate copy
    s2 := make([]int, len(s1))
    copy(s2, s1)

    s1[0] = 999
    fmt.Println("s1:", s1)  // [999 2 3]
    fmt.Println("s2:", s2)  // [1 2 3] ← unaffected
}

▶ Example: Using "copy + append" to insert a slice

GO
package main

import "fmt"

// Insert element val at index i
func insert(s []int, i int, val int) []int {
    // 1. Add 1 position
    s = append(s, 0)

    // 2. Shift the element at index i and all subsequent elements one position to the right
    copy(s[i+1:], s[i:])

    // 3. Add a new element
    s[i] = val

    return s
}

func main() {
    s := []int{1, 2, 4, 5}
    s = insert(s, 2, 3)
    fmt.Println(s)  // [1 2 3 4 5]
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
[1 2 3 4 5]


9. Complete Example: Dynamic Data Collector

Combine all the features of the slices to build a tool for multi-source data collection + deduplication + filtering:

GO
// collector.go
package main

import (
    "fmt"
    "strings"
)

// Data Source Simulation Functions
func fetchFromSource(sourceName string) []string {
    data := map[string][]string{
        "GitHub": {"repo:go", "repo:docker", "issue:bug", "repo:go"},  // last one is duplicate
        "NPM":    {"pkg:react", "pkg:vue", "pkg:react"},              // duplicate
        "PyPI":   {"pkg:requests", "pkg:flask"},
    }
    return data[sourceName]
}

// Removing Duplicates: Using `map` to Create a Hash Set
func deduplicate(data []string) []string {
    seen := make(map[string]bool)
    result := make([]string, 0, len(data))

    for _, item := range data {
        if !seen[item] {
            seen[item] = true
            result = append(result, item)
        }
    }
    return result
}

// Filter: Keep only those starting with "repo:"
func filterRepos(data []string) []string {
    result := make([]string, 0)
    for _, item := range data {
        if strings.HasPrefix(item, "repo:") {
            result = append(result, item)
        }
    }
    return result
}

// Pipeline: Combining Multiple Steps
func collectPipeline() (total, unique, repos int, sources []string) {
    defer func() {
        // Naming Return Values + Defer Collection Statistics
        fmt.Printf("\n[Collection Complete] total=%d unique=%d repos=%d sources=%v\n",
            total, unique, repos, sources)
    }()

    allSources := []string{"GitHub", "NPM", "PyPI"}
    var allData []string

    for _, src := range allSources {
        sources = append(sources, src)
        data := fetchFromSource(src)
        allData = append(allData, data...)
        total += len(data)
    }

    uniqueData := deduplicate(allData)
    unique = len(uniqueData)

    repoData := filterRepos(uniqueData)
    repos = len(repoData)

    fmt.Println("\n=== Deduped Data ===")
    for _, item := range uniqueData {
        fmt.Printf("  %s\n", item)
    }

    fmt.Println("\n=== Filtering Repo Data ===")
    for _, item := range repoData {
        fmt.Printf("  %s\n", item)
    }

    return total, unique, repos, sources
}

func main() {
    collectPipeline()
}

Expected Output:

TEXT 📖 Display only
=== Deduped Data ===
  repo:go
  repo:docker
  issue:bug
  pkg:react
  pkg:vue
  pkg:requests
  pkg:flask

=== Filtering Repo Data ===
  repo:go
  repo:docker

[Collection Complete] total=9 unique=7 repos=2 sources=[GitHub NPM PyPI]
🔥 Common Mistake: Use make([]string, 0, len(data)) to pre-allocate capacity and avoid frequent resizing with append. This is a key performance optimization technique.


❓ FAQ

Q What is the underlying relationship between arrays and slices?
A A slice is essentially a view of an array. A slice consists of 24 bytes: an 8-byte pointer (pointing to an element in the array) + an 8-byte length + an 8-byte capacity. Multiple slices can share the same underlying array.
Q Does append always cause the array to resize?
A Not necessarily. The array only resizes (allocating a new array and copying the contents twice) when len == cap. If there is still space available, the elements are written directly to the original array.
Q Is a slice passed to a function by value or by reference?
A The slice itself (24 bytes) is passed by value, but the underlying array it points to is shared. Therefore, modifying elements within the function affects the external slice, but whether append affects the external slice depends on whether the slice is resized.
Q When is the max parameter in the [low:high:max] format required?
A Use it when you want to limit the size of a slice to prevent append operations from affecting other elements in the original array. Common scenario: creating a sub-slice from a larger slice followed by an append operation.
Q What is the difference between a nil slice and an empty slice?
A A nil slice (such as var s []int) evaluates to true when checked for nil; its len is 0 and its cap is 0. An empty slice (such as []int{} or make([]int, 0)) evaluates to false when compared to nil, with len=0 and cap=0. Both can be appended to, but the JSON serialization results differ: nilnull, empty → [].
Q Is there a built-in method for removing elements from a slice?
A No. Unlike Java, Go does not have list.remove(i). Go implements removal using append(s[:i], s[i+1:]...) (but this does not preserve the order). If you want to preserve the order, you need to manually reorder the elements.
Q How do I copy a slice?
A There are three ways: (1) copy(dst, src) — Copies min(len(dst), len(src)) elements; (2) append([]T(nil), src...) — Copies all elements; (3) Manually using for range.
Q What is the performance difference between slices and arrays when using range?
A Using range on an array copies the entire array (poor performance); using range on a slice copies only 24 bytes (good performance). This is why slices are the de facto standard for "collections."

📖 Summary


📝 Exercises

  1. Basic Problem (Difficulty ⭐): Use slices to implement a reverse([]int) []int function that reverses the elements of a slice. Hint: Use a for loop with a temporary variable, or swap elements in-place. Test case: reverse([]int{1,2,3,4,5})[5 4 3 2 1].

  2. Advanced Problem (Difficulty ⭐⭐): Implement a uniqueSorted(nums []int) []int function: remove duplicates and sort in ascending order. Hint: First sort the array using sort.Ints, then iterate through it to remove duplicates. Given the input []int{3, 1, 4, 1, 5, 9, 2, 6, 5}, the output should be [1 2 3 4 5 6 9].

  3. Challenge Problem (Difficulty ⭐⭐⭐): Implement a sliding window maximum algorithm: maxSlidingWindow(nums []int, k int) []int. For example, nums=[1,3,-1,-3,5,3,6,7], k=3 → Output [3,3,5,5,6,7]. Requirements: (1) Time complexity O(n); (2) Use a deque (which can be simulated using []int) to maintain the window; (3) Explain why a direct traversal would result in a timeout (O(n*k)).

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏