Working with Arrays and Slices in Go
Arrays and slices are fundamental data structures in Go. This guide covers everything you need to know about working with arrays and slices effectively.
Arrays
Arrays in Go are fixed-size sequences of elements of the same type.
Array Declaration
// Basic array declaration
var numbers [5]int
// Array with initialization
var scores [3]int = [3]int{10, 20, 30}
// Short declaration with initialization
grades := [3]int{90, 85, 95}
// Array with size determined by initializer
days := [...]string{"Mon", "Tue", "Wed"}
// Array with sparse initialization
sparse := [5]int{1: 10, 3: 30} // [0, 10, 0, 30, 0]
Array Operations
// Accessing elements
first := numbers[0]
numbers[1] = 42
// Array length
length := len(numbers)
// Array iteration
for i := 0; i < len(numbers); i++ {
fmt.Printf("numbers[%d] = %d\n", i, numbers[i])
}
// Range-based iteration
for index, value := range numbers {
fmt.Printf("numbers[%d] = %d\n", index, value)
}
Multi-dimensional Arrays
// 2D array declaration
var matrix [3][4]int
// 2D array initialization
grid := [2][3]int{
{1, 2, 3},
{4, 5, 6},
}
// Accessing elements
value := grid[1][2] // 6
// Iterating over 2D array
for i := 0; i < len(grid); i++ {
for j := 0; j < len(grid[i]); j++ {
fmt.Printf("%d ", grid[i][j])
}
fmt.Println()
}
Array Comparison
// Arrays of same type and length can be compared
a1 := [3]int{1, 2, 3}
a2 := [3]int{1, 2, 3}
a3 := [3]int{1, 2, 4}
fmt.Println(a1 == a2) // true
fmt.Println(a1 == a3) // false
// Copy array
var copy [3]int
copy = a1
Slices
Slices are dynamic, flexible views into arrays.
Slice Declaration
// Slice declaration
var numbers []int
// Create slice with make
numbers = make([]int, 5) // len=5, cap=5
numbers = make([]int, 5, 10) // len=5, cap=10
// Slice literal
fruits := []string{"apple", "banana", "orange"}
// Slice from array
array := [5]int{1, 2, 3, 4, 5}
slice := array[1:4] // [2, 3, 4]
// Slice with omitted bounds
slice = array[:] // All elements
slice = array[:3] // First three elements
slice = array[3:] // Elements from index 3
Slice Operations
// Append elements
numbers = append(numbers, 1)
numbers = append(numbers, 2, 3, 4)
// Append slice
other := []int{5, 6, 7}
numbers = append(numbers, other...)
// Copy slices
dest := make([]int, len(numbers))
copied := copy(dest, numbers)
// Clear slice
numbers = numbers[:0]
// Delete element at index i
i := 2
numbers = append(numbers[:i], numbers[i+1:]...)
// Insert element at index i
i = 2
numbers = append(numbers[:i], append([]int{100}, numbers[i:]...)...)
// Filter slice
filtered := numbers[:0]
for _, x := range numbers {
if x > 10 {
filtered = append(filtered, x)
}
}
Slice Capacity
// Create slice with capacity
slice := make([]int, 0, 10)
// Get length and capacity
length := len(slice)
capacity := cap(slice)
// Grow slice
for i := 0; i < 20; i++ {
slice = append(slice, i)
fmt.Printf("len=%d cap=%d\n", len(slice), cap(slice))
}
Multi-dimensional Slices
// 2D slice
matrix := make([][]int, 3)
for i := range matrix {
matrix[i] = make([]int, 4)
}
// Jagged slice (rows can have different lengths)
jagged := [][]int{
{1, 2, 3},
{4, 5},
{6, 7, 8, 9},
}
// Accessing elements
value := matrix[1][2]
// Iterating over 2D slice
for i := range matrix {
for j := range matrix[i] {
fmt.Printf("%d ", matrix[i][j])
}
fmt.Println()
}
Best Practices
1. Slice Initialization
// Good: Initialize with expected capacity
data := make([]int, 0, 100)
for i := 0; i < 100; i++ {
data = append(data, i)
}
// Avoid: Growing slice without capacity
var data []int
for i := 0; i < 100; i++ {
data = append(data, i) // May cause reallocations
}
2. Memory Management
// Good: Limit slice growth
const maxSize = 1000
if len(slice) < maxSize {
slice = append(slice, item)
}
// Good: Reuse slice memory
slice = slice[:0] // Clear without deallocating
// Avoid memory leaks
// Original slice holding large array in memory
original := []int{1, 2, 3, 4, 5, /* ... many items ... */}
// Create new slice with only needed elements
needed := make([]int, len(original[:3]))
copy(needed, original[:3])
// Now original can be garbage collected
original = nil
3. Slice Operations
// Good: Pre-allocate slice with known size
data := make([]int, 0, len(items))
for _, item := range items {
if item.IsValid() {
data = append(data, item.Value)
}
}
// Good: Use copy for slice operations
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)
Common Patterns
1. Stack Implementation
type Stack struct {
items []interface{}
}
func (s *Stack) Push(item interface{}) {
s.items = append(s.items, item)
}
func (s *Stack) Pop() (interface{}, bool) {
if len(s.items) == 0 {
return nil, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
2. Queue Implementation
type Queue struct {
items []interface{}
}
func (q *Queue) Enqueue(item interface{}) {
q.items = append(q.items, item)
}
func (q *Queue) Dequeue() (interface{}, bool) {
if len(q.items) == 0 {
return nil, false
}
item := q.items[0]
q.items = q.items[1:]
return item, true
}
3. Ring Buffer
type RingBuffer struct {
data []interface{}
size int
head int
tail int
isFull bool
}
func NewRingBuffer(size int) *RingBuffer {
return &RingBuffer{
data: make([]interface{}, size),
size: size,
}
}
func (r *RingBuffer) Write(item interface{}) bool {
if r.isFull {
return false
}
r.data[r.tail] = item
r.tail = (r.tail + 1) % r.size
r.isFull = r.tail == r.head
return true
}
Performance Considerations
1. Slice Growth
// Pre-allocate for better performance
data := make([]int, 0, 1000)
// Benchmark different growth strategies
func BenchmarkSliceGrowth(b *testing.B) {
for i := 0; i < b.N; i++ {
data := make([]int, 0)
for j := 0; j < 1000; j++ {
data = append(data, j)
}
}
}
2. Copy vs Append
// Copy is faster for known sizes
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)
// Append is more flexible but may reallocate
dst = append([]int(nil), src...)
3. Slice Operations
// Efficient deletion from middle
func remove(slice []int, i int) []int {
copy(slice[i:], slice[i+1:])
return slice[:len(slice)-1]
}
// Efficient insertion into middle
func insert(slice []int, i int, value int) []int {
slice = append(slice, 0)
copy(slice[i+1:], slice[i:])
slice[i] = value
return slice
}
Next Steps
- Learn about maps
- Explore structs
- Study interfaces
- Practice with algorithms