Go Arrays and Slices Tutorial
Arrays and slices are Go's primary sequential data structures. While arrays are fixed-size value types, slices are dynamic, flexible views into underlying arrays that power most list-like operations in Go.
1. Arrays - Fixed-Size Sequences
Arrays are fixed-length, homogeneous collections where the size is part of the type itself.
// Declaration with zero values
var days [7]string
// Literal initialization
primes := [5]int{2, 3, 5, 7, 11}
// Ellipsis for inferred length
temps := [...]float64{98.6, 99.2, 97.9}
// Access/modify elements
primes[0] = 1 // First element (index 0)
len := len(primes) // Length is part of type
Key Characteristics:
- Fixed size - Cannot be resized after creation
- Value type - Copies entire array when assigned or passed
- Memory layout - Contiguous block of elements
- Type includes size -
[5]intand[10]intare distinct types
Arrays Quiz
What happens when you assign one array to another?
2. Slices - Dynamic Views
Slices are flexible, dynamically-sized views into underlying arrays.
// Slice literals
colors := []string{"red", "green", "blue"}
// From array
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // [2, 3, 4]
// Using make
nums := make([]int, 3, 5) // len=3, cap=5
// Common operations
colors = append(colors, "yellow") // Add element
copy(nums, []int{7, 8, 9}) // Copy between slices
Slice Internals:
- Three components: pointer, length, and capacity
- Reference type - Points to underlying array
- Resizable - Via
append()function - Zero value is
nil(no underlying array)
Slices Quiz
What makes a slice different from an array?
3. Slice Operations
Slices support powerful operations for manipulation and access.
s := []int{10, 20, 30, 40, 50}
// Slicing (creates new slice view)
firstTwo := s[:2] // [10, 20]
lastThree := s[2:] // [30, 40, 50]
middle := s[1:4] // [20, 30, 40]
// Length vs Capacity
fmt.Println(len(s), cap(s)) // 5, 5
// Appending (may allocate new array)
s = append(s, 60, 70)
// Copying
dest := make([]int, 2)
copied := copy(dest, s) // Returns number copied
Important Behaviors:
- Slicing creates new views of same array (no copy)
- Appending beyond capacity allocates new array
- Modifications affect all slices sharing same array
- Use
copy()for independent duplicates
Operations Quiz
What happens when you append to a full slice?
4. Memory Efficiency
Understanding slice memory management is crucial for performance.
// Pre-allocate with make
items := make([]string, 0, 100) // len=0, cap=100
// Re-slicing to free memory
bigSlice := make([]byte, 1e6)
// Keep only first 10 elements
bigSlice = bigSlice[:10:10] // Reset capacity
// Clearing without reallocation
names := []string{"Alice", "Bob", "Charlie"}
names = names[:0] // len=0, but keeps capacity
Optimization Techniques:
- Pre-allocate capacity when size is known
- Re-slice to release unused memory
- Reuse slices by resetting length
- Beware of leaks - Slices can keep large arrays alive
Memory Quiz
How can you free memory from a large slice?
5. Common Slice Patterns
Idiomatic Go uses slices in several standard patterns.
// Stack operations
stack := []int{1, 2, 3}
stack = append(stack, 4) // push
top := stack[len(stack)-1] // peek
stack = stack[:len(stack)-1] // pop
// Removing elements
func remove(s []string, i int) []string {
return append(s[:i], s[i+1:]...)
}
// Filtering in place
n := 0
for _, x := range data {
if isValid(x) {
data[n] = x
n++
}
}
data = data[:n]
Useful Patterns:
- Stack operations using append and re-slicing
- In-place filtering by compacting valid elements
- Batch processing by slicing into chunks
- Zero-copy conversions between compatible types
Patterns Quiz
How would you implement a stack with slices?
6. Strings as Byte Slices
Strings behave similarly to read-only byte slices with special syntax.
s := "hello, 世界"
// String as byte slice
bytes := []byte(s) // Copy conversion
str := string(bytes) // Back to string
// Rune slices for Unicode
runes := []rune(s) // Full Unicode characters
sub := string(runes[7:9]) // "世界"
// String slicing
fmt.Println(s[0:5]) // "hello" (byte positions)
Important Notes:
- String slices reference original string memory
- Indexing returns bytes (not characters)
- Use
[]runefor proper Unicode handling - Conversions between
stringand[]bytecopy data
Strings Quiz
What does s[0] return for a string?
7. Performance Tips
Optimizing slice usage can significantly impact application performance.
// Pre-allocate when size is known
result := make([]Item, 0, expectedSize)
// Reuse buffers
var buffer []byte
for _, task := range tasks {
buffer = buffer[:0] // reset
buffer = process(task, buffer)
}
// Avoid unnecessary allocations
func concat(a, b []string) []string {
return append(a, b...)
}
// Capacity hints
names := make([]string, 0, len(users))
for _, u := range users {
names = append(names, u.Name)
}
Optimization Strategies:
- Pre-size slices when possible
- Reuse buffers across operations
- Minimize allocations in hot paths
- Use capacity hints for append-heavy code
- Consider arrays for small, fixed-size collections
Performance Quiz
When should you prefer arrays over slices?