Slicce vs Array in Go

Slice vs Array´

In Go, there are two common data structures for “sequence data”: Array and Slice. They are similar in syntax, but the differences in behavior and usage scenarios are crucial. In short, Array is fixed-length (Static Array), while Slice is dynamic-length (Dynamic Array).

Array

// Declare an Array of 5 int
array := [5]int{1, 2, 3, 4, 5}
// Access or modify an element
array[0] = 100
// Let compiler automatically infer array length
b := [...]string{"Penn", "Teller"}

Slice

Slice is built on top of Array. It does not store data directly but points to the underlying Array while recording the current length and capacity.

func main() {
// Create Slice (length 3, capacity 5)
s := make([]int, 3, 5)
fmt.Println(s, len(s), cap(s)) // [0 0 0] 3 5
// Modify and read
s[0] = 10
s[1] = 20
s[2] = 30
// Expand
s = append(s, 40, 50)
fmt.Println(s, len(s), cap(s)) // [10 20 30 40 50] 5 5
// Expand
s = append(s, 60)
fmt.Println(s, len(s), cap(s)) // [10 20 30 40 50 60] 6 10
// Sub Slice
sub := s[1:4]
fmt.Println(sub) // [20 30 40]
}

Understanding Slice’s Automatic Expansion Mechanism

When performing append on a Slice:

  • If the underlying Array still has space → just extend the length (O(1))
  • If the capacity is insufficient → Go will create a larger Array and copy the old data over (O(n))
func main() {
// Amortized analysis
s := []int{1}
fmt.Println(len(s), cap(s)) // 1 1, O(1)
s = append(s, 1)
fmt.Println(len(s), cap(s)) // 2 2, O(1)
s = append(s, 1)
fmt.Println(len(s), cap(s)) // 3 4, O(n)
s = append(s, 1)
fmt.Println(len(s), cap(s)) // 4 4, O(1)
s = append(s, 1)
fmt.Println(len(s), cap(s)) // 5 8, O(n)
}

You can see that the capacity mostly doubles, this automatic expansion strategy balances performance and memory utilization.

Range

Iterating through the array contents.

func main() {
s := []string{"apple", "banana", "cherry"}
for i, v := range s {
fmt.Println(i, v)
}
}
// 0 apple
// 1 banana
// 2 cherry

: Slice

: is used to take a portion of the Slice, formatted as slice[low:high] which means starting from index low (inclusive) to index high (exclusive):

func main() {
s := []int{10, 20, 30, 40, 50}
fmt.Println(s[1:3]) // [20 30]
fmt.Println(s[:2]) // [10 20], omitting the lower bound means 0
fmt.Println(s[2:]) // [30 40 50], omitting the upper bound means len(s)
}

Although s[a:b] creates a new Slice, it still shares the same underlying array. Modifying sub Slice’s content will affect the original data:

sub := s[1:3]
sub[0] = 99
fmt.Println(s) // [10 99 30 40 50]
fmt.Println(sub) // [99 30]

Copy

Since Slices share the underlying Array, if you want to create a completely independent copy, you should use copy():

s := []int{1, 2, 3}
t := make([]int, len(s))
copy(t, s)
t[0] = 99
fmt.Println(s) // [1 2 3]
fmt.Println(t) // [99 2 3]

Summary

Comparison ItemArraySlice
LengthFixedVariable
Type Definition[N]T[]T
Contains Capacity InformationNoYes
ExpandableNoYes (append)
Passing OverheadCopy entire arrayPass reference (lightweight)
Suitable ScenariosKnown fixed lengthUnknown length, needs flexible data growth

You can start solving problems related to Go Array on Codewars🔗.

Further Reading