Understanding Go Arrays and Slices: Differences, Implementation Details, and Capacity Expansion
This article explains the fundamental differences between Go arrays and slices, describes their memory layout and value semantics, illustrates various slice declaration forms, and details the rules governing slice capacity growth with example code and visual diagrams.
Many developers confuse Go array and slice ; this article examines their differences from a low‑level perspective.
Arrays : In almost all programming languages an array is a contiguous block of memory, and Go is no exception. Each element is accessed by a unique index (or subscript). For example, the literal [5]int{1:10, 2:20} represents an array whose internal layout is a continuous memory region. Because the memory is contiguous, the CPU can compute the address of an element quickly, making iteration fast.
Unlike C, a Go array is a value type, not a pointer to the first element. Assigning an array copies the entire data, so a variable of array type holds the whole array. Conceptually a Go array can be thought of as an ordered struct .
Slices : A slice is a lightweight abstraction over an array that provides three fields—length, capacity, and a pointer to the underlying array. The pointer ( ptr ) references the array, len is the current length, and cap is the total capacity.
There are several ways to declare a slice. For instance, s := make([]byte, 5) creates a slice with length and capacity 5, while s := []byte{...} initializes a slice with the provided elements. Slicing syntax such as s = s[2:4] creates a new slice that shares the same underlying array. A nil slice ( var s []byte ) differs from an empty slice ( s := make([]byte, 0) or s := []byte{} ) in that the nil slice’s pointer is nil, whereas an empty slice’s pointer is non‑nil but points to a zero‑length array. Nevertheless, built‑in functions like append() , len() , and cap() behave the same for both.
Slice Capacity Expansion : Two rules govern how a slice grows:
If the capacity is less than 1,024 elements, the capacity doubles on each growth. Once the size exceeds 1,024, the growth factor becomes 1.25 (i.e., the capacity increases by a quarter of its current size).
If the new capacity still fits within the original array, the slice’s pointer continues to reference that array. If the growth exceeds the original array’s capacity, Go allocates a new backing array and copies the data, leaving the original array unchanged.
Consider the following program that demonstrates these concepts:
import (
"fmt"
)
func main() {
array := [4]int{10, 20, 30, 40}
slice := array[0:2]
newSlice := append(append(append(slice, 50), 100), 150)
newSlice[1] += 1
fmt.Println(slice)
}The program prints:
[10 20]Thus, modifications to newSlice after the append operations do not affect the original slice because the underlying array was reallocated during growth.
References
"Go in Action"
https://blog.golang.org/go-slices-usage-and-internals
Beike Product & Technology
As Beike's official product and technology account, we are committed to building a platform for sharing Beike's product and technology insights, targeting internet/O2O developers and product professionals. We share high-quality original articles, tech salon events, and recruitment information weekly. Welcome to follow us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.