1. go
  2. /data structures
  3. /slices

Understanding Slices in Go Programming

Slices are a fundamental data structure in Go that provides a flexible, dynamic view into an array. This guide covers everything you need to know about working with slices effectively.

Creating Slices

Basic Slice Creation

// Create empty slice var s []int  // Create slice with initial values numbers := []int{1, 2, 3, 4, 5}  // Create slice with make s = make([]int, 5) // len=5, cap=5 s = make([]int, 5, 10) // len=5, cap=10  // Create slice from array arr := [5]int{1, 2, 3, 4, 5} s = arr[1:4] // [2, 3, 4] 

Slice Expressions

// Basic slicing s := []int{1, 2, 3, 4, 5} s1 := s[1:4] // [2, 3, 4] s2 := s[:3] // [1, 2, 3] s3 := s[2:] // [3, 4, 5] s4 := s[:] // [1, 2, 3, 4, 5]  // Full slice expression s5 := s[1:4:5] // length=3, capacity=4 

Slice Operations

Appending Elements

// Append single element s = append(s, 6)  // Append multiple elements s = append(s, 7, 8, 9)  // Append one slice to another s1 := []int{1, 2, 3} s2 := []int{4, 5, 6} s1 = append(s1, s2...) 

Copying Slices

// Copy slices src := []int{1, 2, 3} dst := make([]int, len(src)) copied := copy(dst, src)  // Partial copy dst = make([]int, 2) copied = copy(dst, src) // copies only first 2 elements 

Deleting Elements

// Delete from middle s := []int{1, 2, 3, 4, 5} i := 2 // index to delete s = append(s[:i], s[i+1:]...) // [1, 2, 4, 5]  // Delete from start s = s[1:] // [2, 4, 5]  // Delete from end s = s[:len(s)-1] // [2, 4] 

Memory Management

Capacity Management

// Pre-allocate capacity s := make([]int, 0, 1000) for i := 0; i < 1000; i++ {  s = append(s, i) }  // Check capacity fmt.Printf("len=%d cap=%d\n", len(s), cap(s))  // Grow capacity s = append(s, 1001) // may trigger reallocation 

Memory Leaks Prevention

// Avoid memory leaks original := []int{1, 2, 3, 4, 5} // Wrong: might retain large array leaked := original[1:2]  // Right: copy to new slice safe := make([]int, 1) copy(safe, original[1:2]) 

Best Practices

1. Slice Initialization

// Good: Initialize with expected size s := make([]int, 0, expectedSize)  // Good: Small literal initialization s := []int{1, 2, 3}  // Avoid: Don't use pointer to slice func bad(sp *[]int) {  // Working with pointer to slice }  // Good: Pass slice directly func good(s []int) {  // Working with slice } 

2. Efficient Appending

// Good: Pre-allocate when size is known s := make([]int, 0, size) for i := 0; i < size; i++ {  s = append(s, i) }  // Avoid: Growing one by one without pre-allocation var s []int for i := 0; i < size; i++ {  s = append(s, i) // May cause multiple reallocations } 

3. Safe Slicing

// Good: Check bounds before slicing func safeSlice(s []int, start, end int) ([]int, error) {  if start < 0 || end > len(s) || start > end {  return nil, fmt.Errorf("invalid slice bounds")  }  return s[start:end], nil } 

Common Patterns

1. Stack Operations

// Stack implementation using slice type Stack struct {  items []interface{} }  func (s *Stack) Push(item interface{}) {  s.items = append(s.items, item) }  func (s *Stack) Pop() interface{} {  if len(s.items) == 0 {  return nil  }  item := s.items[len(s.items)-1]  s.items = s.items[:len(s.items)-1]  return item } 

2. Filtering

// Filter slice elements func filter(s []int, fn func(int) bool) []int {  var result []int  for _, v := range s {  if fn(v) {  result = append(result, v)  }  }  return result }  // Usage evens := filter([]int{1, 2, 3, 4}, func(x int) bool {  return x%2 == 0 }) 

3. Map-Reduce Operations

// Map operation func mapSlice(s []int, fn func(int) int) []int {  result := make([]int, len(s))  for i, v := range s {  result[i] = fn(v)  }  return result }  // Reduce operation func reduce(s []int, fn func(int, int) int, initial int) int {  result := initial  for _, v := range s {  result = fn(result, v)  }  return result } 

Performance Considerations

1. Memory Allocation

// Efficient: Single allocation s := make([]int, 0, 1000) for i := 0; i < 1000; i++ {  s = append(s, i) }  // Inefficient: Multiple allocations var s []int for i := 0; i < 1000; i++ {  s = append(s, i) } 

2. Slice Operations

// Efficient: In-place operations func reverse(s []int) {  for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {  s[i], s[j] = s[j], s[i]  } }  // Inefficient: Creating new slice func inefficientReverse(s []int) []int {  result := make([]int, len(s))  for i, v := range s {  result[len(s)-1-i] = v  }  return result } 

Common Mistakes

1. Slice Capacity Issues

// Wrong: Not considering capacity s1 := []int{1, 2, 3, 4, 5} s2 := s1[1:3] s2 = append(s2, 6) // Modifies s1  // Right: Create new slice s2 := make([]int, 2) copy(s2, s1[1:3]) s2 = append(s2, 6) // Doesn't affect s1 

2. Nil Slice vs Empty Slice

// Nil slice var s1 []int fmt.Println(s1 == nil) // true  // Empty slice s2 := make([]int, 0) fmt.Println(s2 == nil) // false  // Both work with append s1 = append(s1, 1) s2 = append(s2, 1) 

3. Range Variable Capture

// Wrong: Capturing range variable var funcs []func() for i := range []int{1, 2, 3} {  funcs = append(funcs, func() {  fmt.Println(i) // Will print last value  }) }  // Right: Create new variable in loop for i := range []int{1, 2, 3} {  i := i // Create new variable  funcs = append(funcs, func() {  fmt.Println(i)  }) } 

Next Steps

  1. Learn about arrays
  2. Explore maps
  3. Study memory model
  4. Practice with algorithms

Additional Resources