Mastering Slices For Dynamic Data Structures: A Deep Dive Into Slice Manipulation And Memory Efficiency
Overview
This chapter takes an in-depth look at slices in Go, a powerful tool for working with dynamic data structures. You’ll start by understanding the basics of slice creation, initialization, and iteration. Then, you’ll explore advanced techniques for growing, shrinking, and manipulating slices without unnecessary memory allocation. Key topics include the append function, slice internals, and memory management techniques like copying and deleting from slices. Additionally, you will learn about Go’s slices package, which provides enhanced tools for sorting, searching, and managing slices efficiently. You’ll also explore the iterator functions introduced in Go 1.23, enabling powerful slice transformations and batch processing. By mastering slices, you’ll be able to work more effectively with Go’s dynamic data structures.
What Are Slices
- Similar to Arrays
- Fixed type
- Dynamically sized
- Flexible
package main
import "fmt"
func main() {
// create an array of names
namesArray := [4]string{"John", "Paul", "George", "Ringo"}
// create a slice of names
namesSlice := []string{"John", "Paul", "George", "Ringo"}
fmt.Println(namesArray)
fmt.Println(namesSlice)
}Iterating Over Slices
Slices can be iterated over in the same ways that arrays can be.
package main
import "fmt"
func main() {
names := []string{"John", "Paul", "George", "Ringo"}
for i, n := range names {
fmt.Printf("%d - %s\n", i, n)
}
}Why Slices?
- Change a slice without an allocation
- Operate on subsections of slices easily
- Dynamically grow the size of a slice
How can you tell the difference between a slice and an array?
Slices don’t have a length in the declaration:
var array [5]string // array
var slice []string // slice
Slice Internals
Think of a slice as having three members:
- Length
- Capacity
- Pointer to the underlying array
package main
// section: slice
type slice struct {
Length int
Capacity int
Array [10]array
}
// section: sliceNOTE: This is not the actual definition, but a way you can think about a slice header.
Appending To Slices
The append keyword allows us to add elements to a slice.
package main
import "fmt"
func main() {
names := []string{}
names = append(names, "John")
// Append multiple items at once
names = append(names, "Sally", "George")
// Append an entire slice to another slice
moreNames := []string{"Bill", "Ginger", "Wilma"}
names = append(names, moreNames...)
fmt.Println(names)
}package main
import "fmt"
func main() {
names := []string{}
names = append(names, "John")
// Append multiple items at once
names = append(names, "Sally", "George")
// Append an entire slice to another slice
moreNames := []string{"Bill", "Ginger", "Wilma"}
names = append(names, moreNames...)
fmt.Println(names)
}Should the slice not have enough space, Go will automatically reallocate the slice to have more capacity.
The Ellipsis Operator (...)
The ellipsis operator (...) has multiple important uses in Go. Understanding when and how to use it is essential for working with slices effectively.
// Example variadic function that accepts any number of integers
func sum(numbers ...int) int {
total := 0
for _, n := range numbers {
total += n
}
return total
}
// Another variadic function
func printNames(prefix string, names ...string) {
fmt.Printf("%s: ", prefix)
for i, name := range names {
if i > 0 {
fmt.Print(", ")
}
fmt.Print(name)
}
fmt.Println()
}
func main() {
// The ellipsis operator (...) has five main uses in Go:
// Use 1: Variadic function parameters
// Allows a function to accept a variable number of arguments
fmt.Println("--- Use 1: Variadic Function Parameters ---")
fmt.Println("sum(1, 2, 3):", sum(1, 2, 3))
fmt.Println("sum(10, 20, 30, 40, 50):", sum(10, 20, 30, 40, 50))
// Use 2: Unpacking slices into variadic functions
// The ... operator "unpacks" a slice into individual arguments
fmt.Println("\n--- Use 2: Unpacking Slices ---")
numbers := []int{5, 10, 15, 20}
fmt.Printf("numbers slice: %v\n", numbers)
fmt.Printf("sum(numbers...): %d\n", sum(numbers...))
// Without ..., this would be a compile error:
// sum(numbers) // ERROR: cannot use numbers (type []int) as type int
// Use 3: Unpacking slices with append
fmt.Println("\n--- Use 3: Append with Slice Unpacking ---")
fruits := []string{"apple", "banana"}
moreFruits := []string{"cherry", "date", "elderberry"}
// Append individual elements
fruits = append(fruits, "fig")
fmt.Printf("After append single: %v\n", fruits)
// Append entire slice - requires ... to unpack
fruits = append(fruits, moreFruits...)
fmt.Printf("After append slice: %v\n", fruits)
// Use 4: Array literals with inferred length
fmt.Println("\n--- Use 4: Array Literals (inferred length) ---")
// The compiler counts the elements and sets the array size
inferredArray := [...]string{"red", "green", "blue"}
fmt.Printf("Array: %v (type: %T, len: %d)\n", inferredArray, inferredArray, len(inferredArray))
// This is equivalent to:
explicitArray := [3]string{"red", "green", "blue"}
fmt.Printf("Array: %v (type: %T, len: %d)\n", explicitArray, explicitArray, len(explicitArray))
// Use 5: Combining variadic functions
fmt.Println("\n--- Use 5: Practical Example ---")
allNames := []string{"Alice", "Bob", "Charlie"}
printNames("Team members", allNames...)
// You can also mix literal values with slice unpacking in some contexts
moreNames := []string{"David", "Eve"}
combined := append(allNames, moreNames...)
printNames("All members", combined...)
// Common gotcha: Can't use ... on left side of assignment
fmt.Println("\n--- Common Gotcha ---")
fmt.Println("The ... operator ONLY works on the right side (as an argument)")
fmt.Println("You CANNOT use it to destructure/unpack on the left side like:")
fmt.Println(" a, b, c... := []int{1, 2, 3, 4, 5} // This is INVALID")
}
package main
import "fmt"
// section: example
// Example variadic function that accepts any number of integers
func sum(numbers ...int) int {
total := 0
for _, n := range numbers {
total += n
}
return total
}
// Another variadic function
func printNames(prefix string, names ...string) {
fmt.Printf("%s: ", prefix)
for i, name := range names {
if i > 0 {
fmt.Print(", ")
}
fmt.Print(name)
}
fmt.Println()
}
func main() {
// The ellipsis operator (...) has five main uses in Go:
// Use 1: Variadic function parameters
// Allows a function to accept a variable number of arguments
fmt.Println("--- Use 1: Variadic Function Parameters ---")
fmt.Println("sum(1, 2, 3):", sum(1, 2, 3))
fmt.Println("sum(10, 20, 30, 40, 50):", sum(10, 20, 30, 40, 50))
// Use 2: Unpacking slices into variadic functions
// The ... operator "unpacks" a slice into individual arguments
fmt.Println("\n--- Use 2: Unpacking Slices ---")
numbers := []int{5, 10, 15, 20}
fmt.Printf("numbers slice: %v\n", numbers)
fmt.Printf("sum(numbers...): %d\n", sum(numbers...))
// Without ..., this would be a compile error:
// sum(numbers) // ERROR: cannot use numbers (type []int) as type int
// Use 3: Unpacking slices with append
fmt.Println("\n--- Use 3: Append with Slice Unpacking ---")
fruits := []string{"apple", "banana"}
moreFruits := []string{"cherry", "date", "elderberry"}
// Append individual elements
fruits = append(fruits, "fig")
fmt.Printf("After append single: %v\n", fruits)
// Append entire slice - requires ... to unpack
fruits = append(fruits, moreFruits...)
fmt.Printf("After append slice: %v\n", fruits)
// Use 4: Array literals with inferred length
fmt.Println("\n--- Use 4: Array Literals (inferred length) ---")
// The compiler counts the elements and sets the array size
inferredArray := [...]string{"red", "green", "blue"}
fmt.Printf("Array: %v (type: %T, len: %d)\n", inferredArray, inferredArray, len(inferredArray))
// This is equivalent to:
explicitArray := [3]string{"red", "green", "blue"}
fmt.Printf("Array: %v (type: %T, len: %d)\n", explicitArray, explicitArray, len(explicitArray))
// Use 5: Combining variadic functions
fmt.Println("\n--- Use 5: Practical Example ---")
allNames := []string{"Alice", "Bob", "Charlie"}
printNames("Team members", allNames...)
// You can also mix literal values with slice unpacking in some contexts
moreNames := []string{"David", "Eve"}
combined := append(allNames, moreNames...)
printNames("All members", combined...)
// Common gotcha: Can't use ... on left side of assignment
fmt.Println("\n--- Common Gotcha ---")
fmt.Println("The ... operator ONLY works on the right side (as an argument)")
fmt.Println("You CANNOT use it to destructure/unpack on the left side like:")
fmt.Println(" a, b, c... := []int{1, 2, 3, 4, 5} // This is INVALID")
}
// section: exampleKey uses:
- Variadic function parameters (accepting variable number of arguments)
- Unpacking slices into individual arguments
- Using with
appendto concatenate slices - Array literals with compiler-inferred length
Deleting From Slices
There are two ways to delete an item from a slice. Either manually, or using the slice.Delete function from the slices package.
Manually deleting from a slice requires you to calculate the specific item you wish to remove:
package main
import "fmt"
func main() {
a := []string{"zero", "one", "two", "three", "four", "five"}
// remove 4 items, starting with the index of 1 (second item in slice)
// append(a[:i], a[i+(number of items to remove):]...) -> i = 1, i+4=5
a = append(a[:1], a[5:]...)
fmt.Println(a)
}package main
import "fmt"
func main() {
a := []string{"zero", "one", "two", "three", "four", "five"}
// remove 4 items, starting with the index of 1 (second item in slice)
// append(a[:i], a[i+(number of items to remove):]...) -> i = 1, i+4=5
a = append(a[:1], a[5:]...)
fmt.Println(a)
}Using the slices.Delete function, you can simply provide the slice, the starting index, and the number of elements to delete:
package main
import (
"fmt"
"slices"
)
func main() {
a := []string{"zero", "one", "two", "three", "four", "five"}
// slices.Delete(s, i, j) -> s = slice having items deleted from, i = start index, j = end index
a = slices.Delete(a, 1, 4)
fmt.Println(a)
}package main
import (
"fmt"
"slices"
)
func main() {
a := []string{"zero", "one", "two", "three", "four", "five"}
// slices.Delete(s, i, j) -> s = slice having items deleted from, i = start index, j = end index
a = slices.Delete(a, 1, 4)
fmt.Println(a)
}The slices package is part of the standard library since Go 1.21
Growing A Slice
names := []string{}
fmt.Println("len:", len(names)) // 0
fmt.Println("cap:", cap(names)) // 0
names = append(names, "John")
fmt.Println("len:", len(names)) // 1
fmt.Println("cap:", cap(names)) // 1
names = append(names, "Paul")
fmt.Println("len:", len(names)) // 2
fmt.Println("cap:", cap(names)) // 2
names = append(names, "George")
fmt.Println("len:", len(names)) // 3
fmt.Println("cap:", cap(names)) // 4
names = append(names, "Ringo")
fmt.Println("len:", len(names)) // 4
fmt.Println("cap:", cap(names)) // 4
names = append(names, "Stu")
fmt.Println("len:", len(names)) // 5
fmt.Println("cap:", cap(names)) // 8package main
import "fmt"
func main() {
// section: code
names := []string{}
fmt.Println("len:", len(names)) // 0
fmt.Println("cap:", cap(names)) // 0
names = append(names, "John")
fmt.Println("len:", len(names)) // 1
fmt.Println("cap:", cap(names)) // 1
names = append(names, "Paul")
fmt.Println("len:", len(names)) // 2
fmt.Println("cap:", cap(names)) // 2
names = append(names, "George")
fmt.Println("len:", len(names)) // 3
fmt.Println("cap:", cap(names)) // 4
names = append(names, "Ringo")
fmt.Println("len:", len(names)) // 4
fmt.Println("cap:", cap(names)) // 4
names = append(names, "Stu")
// section: len
fmt.Println("len:", len(names)) // 5
fmt.Println("cap:", cap(names)) // 8
// section: len
// section: code
}Len And Cap
package main
import "fmt"
func main() {
// section: code
names := []string{}
fmt.Println("len:", len(names)) // 0
fmt.Println("cap:", cap(names)) // 0
names = append(names, "John")
fmt.Println("len:", len(names)) // 1
fmt.Println("cap:", cap(names)) // 1
names = append(names, "Paul")
fmt.Println("len:", len(names)) // 2
fmt.Println("cap:", cap(names)) // 2
names = append(names, "George")
fmt.Println("len:", len(names)) // 3
fmt.Println("cap:", cap(names)) // 4
names = append(names, "Ringo")
fmt.Println("len:", len(names)) // 4
fmt.Println("cap:", cap(names)) // 4
names = append(names, "Stu")
// section: len
fmt.Println("len:", len(names)) // 5
fmt.Println("cap:", cap(names)) // 8
// section: len
// section: code
}The len keyword tells us how many elements the slice actually has.
The cap keyword tells us the capacity of the slice, or how many elements it can have.
What Happens When A Slice Grows
Given the following slice:
┌────────────────────────────────────────────────┐
│ Slice Header │
├────────────────────────────────────────────────┤
│ Len -> 4 │
│ │
│ Cap -> 4 │
│ ┌─────────────────┐ │
│ Arr -> Array Pointer -> │Underlying Array │ │
│ │ │ │
│ │┌─┬─┬─┬─┐ │ │
│ ││A│B│C│D│ │ │
│ │└─┴─┴─┴─┘ │ │
│ └─────────────────┘ │
└────────────────────────────────────────────────┘
If we append the values E, F, and G, it will force the slice to expand, as it currently has no capacity for the new values.
It will create a new underlying array, copy the original values into the new one, and add the new values as well.
┌────────────────────────────────────────────────┐
│ append(slice, "E", "F", "G") │
├────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │Original Array │ │New Array │ │
│ │ │ │ │ │
│ │┌─┬─┬─┬─┐ │───────>│┌─┬─┬─┬─┬─┬─┬─┬─┐│ │
│ ││A│B│C│D│ │ ││A│B│C│D│E│F│G│ ││ │
│ │└─┴─┴─┴─┘ │ │└─┴─┴─┴─┴─┴─┴─┴─┘│ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└────────────────────────────────────────────────┘
This is what the new slice will look like:
┌────────────────────────────────────────────────┐
│ Slice Header │
├────────────────────────────────────────────────┤
│ Len -> 7 │
│ │
│ Cap -> 8 │
│ ┌─────────────────┐ │
│ Arr -> Array Pointer -> │Underlying Array │ │
│ │ │ │
│ │┌─┬─┬─┬─┬─┬─┬─┬─┐│ │
│ ││A│B│C│D│E│F│G│ ││ │
│ │└─┴─┴─┴─┴─┴─┴─┴─┘│ │
│ └─────────────────┘ │
└────────────────────────────────────────────────┘
If the original underlying array is not reference by any other part of the program, it will be marked for garbage collection.
Making A Slice
Slices can also be created using the make keyword.
package main
import "fmt"
func main() {
a := []string{}
b := make([]string, 0)
var c []string
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}package main
import "fmt"
func main() {
a := []string{}
b := make([]string, 0)
var c []string
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}All of the slices are functionally equivalent, but the first version is easier to read (and write).
Make With Length And Capacity
The make keyword allows us to define the starting “length” of the slice, and optionally, the starting “capacity” of the slice.
package main
import "fmt"
func main() {
a := make([]int, 1, 3)
fmt.Println(a) // [0]
fmt.Println(len(a)) // 1
fmt.Println(cap(a)) // 3
}package main
import "fmt"
func main() {
a := make([]int, 1, 3)
fmt.Println(a) // [0]
fmt.Println(len(a)) // 1
fmt.Println(cap(a)) // 3
}It’s important to remember that even though you allocated extra capacity, you can’t access that capacity until you assign a value to it
Make And Append
Be careful when using both make and append, as you may inadvertantly create zero values in your slice.
package main
import "fmt"
func main() {
a := make([]string, 2)
a = append(a, "foo", "bar")
fmt.Printf("%q", a)
}Two Dimensional Slices
Go’s slices are one-dimensional. To create an equivalent of a 2D slice, it is necessary to define an slice-of-slices.
Because slices are variable-length, it is possible to have each inner slice be a different length.
type Modules [][]string
course := Modules{
[]string{"Chapter One: Syntax"},
[]string{"Chapter Two: Arrays and Slices"},
[]string{"Chapter Three: Maps", "Chapter Four: Packages"},
}package main
import "fmt"
func main() {
// a slice of string slices
// section: slice
type Modules [][]string
course := Modules{
[]string{"Chapter One: Syntax"},
[]string{"Chapter Two: Arrays and Slices"},
[]string{"Chapter Three: Maps", "Chapter Four: Packages"},
}
// section: slice
fmt.Println(course)
}Slice Subsets
Subsets of a slice (or a slice of a slice) allow us to work with just section of a slice.
package main
import (
"fmt"
)
// slice[starting_index : (starting_index + length)]
func main() {
names := []string{"John", "Paul", "George", "Ringo"}
fmt.Println(names) // [John Paul George Ringo]
// Get 2 elements starting with the second element (index 1)
fmt.Println(names[1:3]) // [Paul George] - names[1:1+2]
// functionally equivalent
fmt.Println(names[2:len(names)]) // [George Ringo]
fmt.Println(names[2:]) // [George Ringo]
// functionally equivalent
fmt.Println(names[0:2]) // [John Paul]
fmt.Println(names[:2]) // [John Paul]
}package main
import (
"fmt"
)
// slice[starting_index : (starting_index + length)]
func main() {
names := []string{"John", "Paul", "George", "Ringo"}
fmt.Println(names) // [John Paul George Ringo]
// Get 2 elements starting with the second element (index 1)
fmt.Println(names[1:3]) // [Paul George] - names[1:1+2]
// functionally equivalent
fmt.Println(names[2:len(names)]) // [George Ringo]
fmt.Println(names[2:]) // [George Ringo]
// functionally equivalent
fmt.Println(names[0:2]) // [John Paul]
fmt.Println(names[:2]) // [John Paul]
}Note: You can not use negative numbers in slices. The following is invalid syntax:
names[:-2]
This will result in an error:
invalid slice index -2 (index must be non-negative)
Mutating Slice Subsets
It is important to remember that when grabbing a subset of a slice, you are just getting a “window” into that slice.
If you mutate the subset, you mutate the original slice.
// slice[starting_index : (starting_index + length)]
func main() {
names := []string{"John", "Paul", "George", "Ringo"}
fmt.Println(names) // [John Paul George Ringo]
// Get the first three elements of the `names` slice
guitars := names[:3]
fmt.Println(guitars) // [John Paul George]
for i, g := range guitars {
guitars[i] = strings.ToUpper(g)
}
// Print out our original slice of names
fmt.Println("names: ", names)
// Print out the guitars slice
fmt.Println("guitars:", guitars)
}
package main
import (
"fmt"
"strings"
)
// section: main
// slice[starting_index : (starting_index + length)]
func main() {
names := []string{"John", "Paul", "George", "Ringo"}
fmt.Println(names) // [John Paul George Ringo]
// Get the first three elements of the `names` slice
guitars := names[:3]
fmt.Println(guitars) // [John Paul George]
for i, g := range guitars {
guitars[i] = strings.ToUpper(g)
}
// Print out our original slice of names
fmt.Println("names: ", names)
// Print out the guitars slice
fmt.Println("guitars:", guitars)
}
// section: mainSub-slice Sharing After Append
A common gotcha occurs when using append() on a slice that has spare capacity. If the capacity is not exceeded, the new slice shares the same underlying array as the original, meaning modifications can affect both slices.
func main() {
// Create a slice with length 2 but capacity 4
// This means there's room to append without reallocation
a := make([]string, 2, 4)
a[0] = "apple"
a[1] = "banana"
fmt.Printf("Initial slice a: %v (len=%d, cap=%d)\n", a, len(a), cap(a))
// Create a new slice by appending to 'a'
// Since capacity is not exceeded, 'b' shares the same underlying array
b := append(a, "cherry")
fmt.Printf("After append, slice b: %v (len=%d, cap=%d)\n", b, len(b), cap(b))
// Modifying 'b' at index 0 affects 'a' because they share the underlying array
b[0] = "MODIFIED"
fmt.Println("\nAfter modifying b[0]:")
fmt.Printf("slice a: %v\n", a) // a is affected!
fmt.Printf("slice b: %v\n", b)
fmt.Println("\n--- Example 2: Exceeding capacity ---")
// Now let's exceed the capacity
c := make([]string, 2, 2)
c[0] = "dog"
c[1] = "cat"
fmt.Printf("\nInitial slice c: %v (len=%d, cap=%d)\n", c, len(c), cap(c))
// Appending here exceeds capacity, so a new array is allocated
d := append(c, "bird")
fmt.Printf("After append, slice d: %v (len=%d, cap=%d)\n", d, len(d), cap(d))
// Modifying 'd' does NOT affect 'c' because they use different arrays
d[0] = "MODIFIED"
fmt.Println("\nAfter modifying d[0]:")
fmt.Printf("slice c: %v\n", c) // c is NOT affected
fmt.Printf("slice d: %v\n", d)
}
package main
import "fmt"
// section: example
func main() {
// Create a slice with length 2 but capacity 4
// This means there's room to append without reallocation
a := make([]string, 2, 4)
a[0] = "apple"
a[1] = "banana"
fmt.Printf("Initial slice a: %v (len=%d, cap=%d)\n", a, len(a), cap(a))
// Create a new slice by appending to 'a'
// Since capacity is not exceeded, 'b' shares the same underlying array
b := append(a, "cherry")
fmt.Printf("After append, slice b: %v (len=%d, cap=%d)\n", b, len(b), cap(b))
// Modifying 'b' at index 0 affects 'a' because they share the underlying array
b[0] = "MODIFIED"
fmt.Println("\nAfter modifying b[0]:")
fmt.Printf("slice a: %v\n", a) // a is affected!
fmt.Printf("slice b: %v\n", b)
fmt.Println("\n--- Example 2: Exceeding capacity ---")
// Now let's exceed the capacity
c := make([]string, 2, 2)
c[0] = "dog"
c[1] = "cat"
fmt.Printf("\nInitial slice c: %v (len=%d, cap=%d)\n", c, len(c), cap(c))
// Appending here exceeds capacity, so a new array is allocated
d := append(c, "bird")
fmt.Printf("After append, slice d: %v (len=%d, cap=%d)\n", d, len(d), cap(d))
// Modifying 'd' does NOT affect 'c' because they use different arrays
d[0] = "MODIFIED"
fmt.Println("\nAfter modifying d[0]:")
fmt.Printf("slice c: %v\n", c) // c is NOT affected
fmt.Printf("slice d: %v\n", d)
}
// section: exampleKey takeaway: When capacity is not exceeded, append creates a new slice header but reuses the underlying array. When capacity is exceeded, a new array is allocated and data is copied.
Copy
You can use the copy keyword to make a copy without sharing reference to the original underlying array.
package main
import "fmt"
func main() {
original := []string{"carrot", "potato", "cucumber", "onion"}
// create a new reference to the existing slice
ref := original
// initialize a variable named 'dup' to the
// same size as the original slice
dup := make([]string, len(original))
// copy the values from original to dup
// NOTE: if the slices are not the same size, it will
// only copy what it has space for (length)
copy(dup, original)
// changing either `ref` or `original` will change either
// of them as they still share the reference to the same
// backing array
ref[0] = "zuchinni"
original[1] = "tomato"
fmt.Println("original: ", original)
fmt.Println("ref: ", ref)
fmt.Println("dup: ", dup)
}package main
import "fmt"
func main() {
original := []string{"carrot", "potato", "cucumber", "onion"}
// create a new reference to the existing slice
ref := original
// initialize a variable named 'dup' to the
// same size as the original slice
dup := make([]string, len(original))
// copy the values from original to dup
// NOTE: if the slices are not the same size, it will
// only copy what it has space for (length)
copy(dup, original)
// changing either `ref` or `original` will change either
// of them as they still share the reference to the same
// backing array
ref[0] = "zuchinni"
original[1] = "tomato"
fmt.Println("original: ", original)
fmt.Println("ref: ", ref)
fmt.Println("dup: ", dup)
}Copy Behavior With Different Sizes
The copy() function copies min(len(src), len(dst)) elements. Understanding this behavior is crucial to avoid common pitfalls.
func main() {
// The copy() function copies min(len(src), len(dst)) elements
// This means it only copies as many elements as will fit
// Example 1: Copying to a zero-size slice (copies 0 elements)
fmt.Println("--- Example 1: Copying to zero-size slice ---")
source1 := []string{"apple", "banana", "cherry"}
dest1 := []string{} // zero length
n1 := copy(dest1, source1)
fmt.Printf("Source: %v (len=%d)\n", source1, len(source1))
fmt.Printf("Dest: %v (len=%d)\n", dest1, len(dest1))
fmt.Printf("Copied: %d elements\n", n1)
// Example 2: Copying to same-size slice (copies all elements)
fmt.Println("\n--- Example 2: Copying to same-size slice ---")
source2 := []string{"dog", "cat", "bird"}
dest2 := make([]string, 3)
n2 := copy(dest2, source2)
fmt.Printf("Source: %v (len=%d)\n", source2, len(source2))
fmt.Printf("Dest: %v (len=%d)\n", dest2, len(dest2))
fmt.Printf("Copied: %d elements\n", n2)
// Example 3: Copying to larger slice (copies only source elements)
fmt.Println("\n--- Example 3: Copying to larger slice ---")
source3 := []string{"red", "green"}
dest3 := make([]string, 5) // larger than source
n3 := copy(dest3, source3)
fmt.Printf("Source: %v (len=%d)\n", source3, len(source3))
fmt.Printf("Dest: %v (len=%d)\n", dest3, len(dest3))
fmt.Printf("Copied: %d elements\n", n3)
fmt.Println("Note: Remaining elements in dest are zero values")
// Example 4: Copying to smaller slice (partial copy)
fmt.Println("\n--- Example 4: Copying to smaller slice (partial copy) ---")
source4 := []string{"alpha", "beta", "gamma", "delta", "epsilon"}
dest4 := make([]string, 2) // smaller than source
n4 := copy(dest4, source4)
fmt.Printf("Source: %v (len=%d)\n", source4, len(source4))
fmt.Printf("Dest: %v (len=%d)\n", dest4, len(dest4))
fmt.Printf("Copied: %d elements\n", n4)
fmt.Println("Note: Only the first 2 elements were copied")
// Example 5: Common pitfall - using capacity instead of length
fmt.Println("\n--- Example 5: Capacity vs Length pitfall ---")
source5 := []int{10, 20, 30, 40, 50}
dest5 := make([]int, 0, 10) // length is 0, but capacity is 10
n5 := copy(dest5, source5)
fmt.Printf("Source: %v (len=%d)\n", source5, len(source5))
fmt.Printf("Dest: %v (len=%d, cap=%d)\n", dest5, len(dest5), cap(dest5))
fmt.Printf("Copied: %d elements\n", n5)
fmt.Println("Note: Even though capacity is 10, length is 0, so nothing is copied!")
// Solution: ensure destination has correct length
fmt.Println("\n--- Example 6: Correct approach with length ---")
source6 := []int{10, 20, 30, 40, 50}
dest6 := make([]int, len(source6)) // length matches source
n6 := copy(dest6, source6)
fmt.Printf("Source: %v (len=%d)\n", source6, len(source6))
fmt.Printf("Dest: %v (len=%d, cap=%d)\n", dest6, len(dest6), cap(dest6))
fmt.Printf("Copied: %d elements\n", n6)
}
package main
import "fmt"
// section: example
func main() {
// The copy() function copies min(len(src), len(dst)) elements
// This means it only copies as many elements as will fit
// Example 1: Copying to a zero-size slice (copies 0 elements)
fmt.Println("--- Example 1: Copying to zero-size slice ---")
source1 := []string{"apple", "banana", "cherry"}
dest1 := []string{} // zero length
n1 := copy(dest1, source1)
fmt.Printf("Source: %v (len=%d)\n", source1, len(source1))
fmt.Printf("Dest: %v (len=%d)\n", dest1, len(dest1))
fmt.Printf("Copied: %d elements\n", n1)
// Example 2: Copying to same-size slice (copies all elements)
fmt.Println("\n--- Example 2: Copying to same-size slice ---")
source2 := []string{"dog", "cat", "bird"}
dest2 := make([]string, 3)
n2 := copy(dest2, source2)
fmt.Printf("Source: %v (len=%d)\n", source2, len(source2))
fmt.Printf("Dest: %v (len=%d)\n", dest2, len(dest2))
fmt.Printf("Copied: %d elements\n", n2)
// Example 3: Copying to larger slice (copies only source elements)
fmt.Println("\n--- Example 3: Copying to larger slice ---")
source3 := []string{"red", "green"}
dest3 := make([]string, 5) // larger than source
n3 := copy(dest3, source3)
fmt.Printf("Source: %v (len=%d)\n", source3, len(source3))
fmt.Printf("Dest: %v (len=%d)\n", dest3, len(dest3))
fmt.Printf("Copied: %d elements\n", n3)
fmt.Println("Note: Remaining elements in dest are zero values")
// Example 4: Copying to smaller slice (partial copy)
fmt.Println("\n--- Example 4: Copying to smaller slice (partial copy) ---")
source4 := []string{"alpha", "beta", "gamma", "delta", "epsilon"}
dest4 := make([]string, 2) // smaller than source
n4 := copy(dest4, source4)
fmt.Printf("Source: %v (len=%d)\n", source4, len(source4))
fmt.Printf("Dest: %v (len=%d)\n", dest4, len(dest4))
fmt.Printf("Copied: %d elements\n", n4)
fmt.Println("Note: Only the first 2 elements were copied")
// Example 5: Common pitfall - using capacity instead of length
fmt.Println("\n--- Example 5: Capacity vs Length pitfall ---")
source5 := []int{10, 20, 30, 40, 50}
dest5 := make([]int, 0, 10) // length is 0, but capacity is 10
n5 := copy(dest5, source5)
fmt.Printf("Source: %v (len=%d)\n", source5, len(source5))
fmt.Printf("Dest: %v (len=%d, cap=%d)\n", dest5, len(dest5), cap(dest5))
fmt.Printf("Copied: %d elements\n", n5)
fmt.Println("Note: Even though capacity is 10, length is 0, so nothing is copied!")
// Solution: ensure destination has correct length
fmt.Println("\n--- Example 6: Correct approach with length ---")
source6 := []int{10, 20, 30, 40, 50}
dest6 := make([]int, len(source6)) // length matches source
n6 := copy(dest6, source6)
fmt.Printf("Source: %v (len=%d)\n", source6, len(source6))
fmt.Printf("Dest: %v (len=%d, cap=%d)\n", dest6, len(dest6), cap(dest6))
fmt.Printf("Copied: %d elements\n", n6)
}
// section: exampleImportant: Copy uses the length (not capacity) of the destination slice to determine how many elements to copy.
Copy And Arrays
The copy() built-in function only works with slices, not arrays. However, you can use slice notation (array[:]) to create a slice view of an array.
func main() {
// The copy() built-in function works with slices, not arrays
// Example 1: Attempting to copy FROM an array (compile error)
// Uncomment to see the error:
// a := [2]int{1, 2}
// s := []int{3, 4}
// copy(s, a) // Compile error: cannot use a (type [2]int) as type []int
// Example 2: Attempting to copy TO an array (compile error)
// Uncomment to see the error:
// s := []int{1, 2}
// a := [2]int{3, 4}
// copy(a, s) // Compile error: first argument to copy should be a slice
// Workaround: Convert array to slice using slice notation
fmt.Println("--- Workaround: Array to Slice Conversion ---")
// Copy FROM an array: use array[:] to create a slice view
sourceArray := [4]int{10, 20, 30, 40}
destSlice := make([]int, 4)
// Convert array to slice using [:]
copy(destSlice, sourceArray[:])
fmt.Printf("Source array: %v\n", sourceArray)
fmt.Printf("Dest slice: %v\n", destSlice)
// Copy TO an array: use array[:] to create a slice view
fmt.Println("\n--- Copy to an array using slice conversion ---")
sourceSlice := []int{100, 200, 300}
destArray := [3]int{}
// Convert array to slice using [:] to allow copy
copy(destArray[:], sourceSlice)
fmt.Printf("Source slice: %v\n", sourceSlice)
fmt.Printf("Dest array: %v\n", destArray)
// Important: The slice view shares the array's underlying data
fmt.Println("\n--- Understanding array[:] creates a slice view ---")
arr := [3]string{"a", "b", "c"}
sliceView := arr[:]
fmt.Printf("Original array: %v (type: %T)\n", arr, arr)
fmt.Printf("Slice view: %v (type: %T)\n", sliceView, sliceView)
// Modifying through the slice view affects the array
sliceView[0] = "MODIFIED"
fmt.Printf("After modifying slice view:\n")
fmt.Printf("Array: %v\n", arr)
fmt.Printf("Slice view: %v\n", sliceView)
}
package main
import "fmt"
// section: example
func main() {
// The copy() built-in function works with slices, not arrays
// Example 1: Attempting to copy FROM an array (compile error)
// Uncomment to see the error:
// a := [2]int{1, 2}
// s := []int{3, 4}
// copy(s, a) // Compile error: cannot use a (type [2]int) as type []int
// Example 2: Attempting to copy TO an array (compile error)
// Uncomment to see the error:
// s := []int{1, 2}
// a := [2]int{3, 4}
// copy(a, s) // Compile error: first argument to copy should be a slice
// Workaround: Convert array to slice using slice notation
fmt.Println("--- Workaround: Array to Slice Conversion ---")
// Copy FROM an array: use array[:] to create a slice view
sourceArray := [4]int{10, 20, 30, 40}
destSlice := make([]int, 4)
// Convert array to slice using [:]
copy(destSlice, sourceArray[:])
fmt.Printf("Source array: %v\n", sourceArray)
fmt.Printf("Dest slice: %v\n", destSlice)
// Copy TO an array: use array[:] to create a slice view
fmt.Println("\n--- Copy to an array using slice conversion ---")
sourceSlice := []int{100, 200, 300}
destArray := [3]int{}
// Convert array to slice using [:] to allow copy
copy(destArray[:], sourceSlice)
fmt.Printf("Source slice: %v\n", sourceSlice)
fmt.Printf("Dest array: %v\n", destArray)
// Important: The slice view shares the array's underlying data
fmt.Println("\n--- Understanding array[:] creates a slice view ---")
arr := [3]string{"a", "b", "c"}
sliceView := arr[:]
fmt.Printf("Original array: %v (type: %T)\n", arr, arr)
fmt.Printf("Slice view: %v (type: %T)\n", sliceView, sliceView)
// Modifying through the slice view affects the array
sliceView[0] = "MODIFIED"
fmt.Printf("After modifying slice view:\n")
fmt.Printf("Array: %v\n", arr)
fmt.Printf("Slice view: %v\n", sliceView)
}
// section: exampleNote: Using array[:] creates a slice that shares the array’s underlying data.
Slices Package
In release 1.21, the slices package was introduced. It includes many useful functions for sorting, managing, and searching slices. While covering each and every function in the package is beyond the scope of this training, we will cover the more common ones used.
Clip
Clip removes unused capacity from the slice. This can be used to release memory back to the runtime if you have had a large amount if items in a slice and have since reduced the length (or in use items).
fmt.Printf("len: %d, capacity: %d\n", len(ints), cap(ints))
ints = slices.Clip(ints)
fmt.Printf("len: %d, capacity: %d\n", len(ints), cap(ints))package main
import (
"fmt"
"slices"
)
func main() {
ints := make([]int, 25, 500)
// section: example
fmt.Printf("len: %d, capacity: %d\n", len(ints), cap(ints))
ints = slices.Clip(ints)
fmt.Printf("len: %d, capacity: %d\n", len(ints), cap(ints))
// section: example
}Output:
len: 25, capacity: 500
len: 25, capacity: 25len: 25, capacity: 500
len: 25, capacity: 25Clone
Clone returns a copy of the slice. All values are copied by assignment, which means that this is considered a shallow copy of elements in the slice. A shallow copy means that the original memory addresses are still in use, so it’s possible you might still make changes to the original data. While this normally isn’t a problem, it’s important to keep in mind if you want a truly independent copy of the data.
original := []int{0, 1, 2, 4, 5}
cloned := slices.Clone(original)
cloned[0] = 9
cloned[1] = 8
cloned[2] = 7
cloned[3] = 6
cloned[4] = 5
fmt.Printf("original: %v\n", original)
fmt.Printf("cloned : %v\n", cloned)package main
import (
"fmt"
"slices"
)
func main() {
// section: example
original := []int{0, 1, 2, 4, 5}
cloned := slices.Clone(original)
cloned[0] = 9
cloned[1] = 8
cloned[2] = 7
cloned[3] = 6
cloned[4] = 5
fmt.Printf("original: %v\n", original)
fmt.Printf("cloned : %v\n", cloned)
// section: example
}Output:
original: [0 1 2 4 5]
cloned : [9 8 7 6 5]original: [0 1 2 4 5]
cloned : [9 8 7 6 5]Clone With Sub Slices
Clone is especially useful when taking a subslice of a slice. Using slices.Clone instead of directly slicing will allow you to mututate (or make changes) to the subslice without directly affecting the parent slice you sliced the data from.
Take the following example. If you take a subslice of the parent, and make changes to the sliced elements, it will also make changes to the parent slice as well:
original := []int{0, 1, 2, 4, 5}
cloned := original[1:4]
cloned[0] = 9
cloned[1] = 8
cloned[2] = 7
fmt.Printf("original: %v\n", original)
fmt.Printf("cloned : %v\n", cloned)package main
import (
"fmt"
)
func main() {
// section: example
original := []int{0, 1, 2, 4, 5}
cloned := original[1:4]
cloned[0] = 9
cloned[1] = 8
cloned[2] = 7
fmt.Printf("original: %v\n", original)
fmt.Printf("cloned : %v\n", cloned)
// section: example
}Output:
original: [0 9 8 7 5]
cloned : [9 8 7]original: [0 9 8 7 5]
cloned : [9 8 7]To avoid this, you can use the slices.Clone function and wrap the subslice to make a copy instead of a reference and no longer affect the parent if you make changes to the child:
original := []int{0, 1, 2, 4, 5}
cloned := slices.Clone(original[1:4]) // <-- clone the subslice
cloned[0] = 9
cloned[1] = 8
cloned[2] = 7
fmt.Printf("original: %v\n", original)
fmt.Printf("cloned : %v\n", cloned)package main
import (
"fmt"
"slices"
)
func main() {
// section: example
original := []int{0, 1, 2, 4, 5}
cloned := slices.Clone(original[1:4]) // <-- clone the subslice
cloned[0] = 9
cloned[1] = 8
cloned[2] = 7
fmt.Printf("original: %v\n", original)
fmt.Printf("cloned : %v\n", cloned)
// section: example
}Output:
original: [0 1 2 4 5]
cloned : [9 8 7]original: [0 1 2 4 5]
cloned : [9 8 7]Notice that the parent slice is no longer affected by the changes made by the subslice.
BinarySearch
BinarySearch searches for a target value in a sorted slice using binary search. It returns the position where the target is found, or where it would be inserted to maintain the sorted order, and a boolean indicating whether the target was found.
Important: The slice must be sorted in ascending order for BinarySearch to work correctly.
// Binary search requires a sorted slice
numbers := []int{1, 3, 5, 7, 9, 11, 13, 15}
// Search for existing value
index, found := slices.BinarySearch(numbers, 7)
fmt.Printf("Search for 7: index=%d, found=%v\n", index, found)
// Search for non-existing value
index, found = slices.BinarySearch(numbers, 8)
fmt.Printf("Search for 8: index=%d, found=%v\n", index, found)
// When not found, index indicates where to insert
fmt.Printf("If we insert 8 at index %d: %v\n", index,
slices.Insert(numbers, index, 8))package main
import (
"fmt"
"slices"
)
func main() {
// section: example
// Binary search requires a sorted slice
numbers := []int{1, 3, 5, 7, 9, 11, 13, 15}
// Search for existing value
index, found := slices.BinarySearch(numbers, 7)
fmt.Printf("Search for 7: index=%d, found=%v\n", index, found)
// Search for non-existing value
index, found = slices.BinarySearch(numbers, 8)
fmt.Printf("Search for 8: index=%d, found=%v\n", index, found)
// When not found, index indicates where to insert
fmt.Printf("If we insert 8 at index %d: %v\n", index,
slices.Insert(numbers, index, 8))
// section: example
}Output:
Search for 7: index=3, found=true
Search for 8: index=4, found=false
If we insert 8 at index 4: [1 3 5 7 8 9 11 13 15]Search for 7: index=3, found=true
Search for 8: index=4, found=false
If we insert 8 at index 4: [1 3 5 7 8 9 11 13 15]Compact
Compact replaces consecutive runs of equal elements with a single copy.
The documentation doesn’t state that the slice already needs to be sorted for the function to work. Upon a quick test, it shows that compact does in fact require a sorted slice to work properly. Luckily, the slices package made sorting very easy as well.
strings := []string{"Apple", "Orange", "Apple", "Banana"}
ints := []int{0, 1, 1, 2, 0, 5, 7, 5}
strings = slices.Compact(strings)
ints = slices.Compact(ints)
fmt.Printf("Not Sorted - Compact does not work\n")
fmt.Println(strings)
fmt.Println(ints)
fmt.Printf("\nSorted - Compact now works as expected\n")
slices.Sort(ints)
slices.Sort(strings)
strings = slices.Compact(strings)
ints = slices.Compact(ints)
fmt.Println(strings)
fmt.Println(ints)
package main
import (
"fmt"
"slices"
)
func main() {
// section: example
strings := []string{"Apple", "Orange", "Apple", "Banana"}
ints := []int{0, 1, 1, 2, 0, 5, 7, 5}
strings = slices.Compact(strings)
ints = slices.Compact(ints)
fmt.Printf("Not Sorted - Compact does not work\n")
fmt.Println(strings)
fmt.Println(ints)
fmt.Printf("\nSorted - Compact now works as expected\n")
slices.Sort(ints)
slices.Sort(strings)
strings = slices.Compact(strings)
ints = slices.Compact(ints)
fmt.Println(strings)
fmt.Println(ints)
// section: example
}Output:
Not Sorted - Compact does not work
[Apple Orange Apple Banana]
[0 1 2 0 5 7 5]
Sorted - Compact now works as expected
[Apple Banana Orange]
[0 1 2 5 7]Not Sorted - Compact does not work
[Apple Orange Apple Banana]
[0 1 2 0 5 7 5]
Sorted - Compact now works as expected
[Apple Banana Orange]
[0 1 2 5 7]Compare
Compare compares two slices element-wise and returns an integer comparing the two slices. It returns -1 if the first slice is less than the second, 0 if they are equal, and +1 if the first slice is greater.
slice1 := []int{1, 2, 3}
slice2 := []int{1, 2, 4}
slice3 := []int{1, 2, 3, 4}
// Compare returns:
// -1 if slice1 < slice2
// 0 if slice1 == slice2
// +1 if slice1 > slice2
fmt.Printf("Compare([1,2,3], [1,2,4]): %d\n", slices.Compare(slice1, slice2))
fmt.Printf("Compare([1,2,3], [1,2,3]): %d\n", slices.Compare(slice1, slice1))
fmt.Printf("Compare([1,2,4], [1,2,3]): %d\n", slices.Compare(slice2, slice1))
fmt.Printf("Compare([1,2,3], [1,2,3,4]): %d\n", slices.Compare(slice1, slice3))package main
import (
"fmt"
"slices"
)
func main() {
// section: example
slice1 := []int{1, 2, 3}
slice2 := []int{1, 2, 4}
slice3 := []int{1, 2, 3, 4}
// Compare returns:
// -1 if slice1 < slice2
// 0 if slice1 == slice2
// +1 if slice1 > slice2
fmt.Printf("Compare([1,2,3], [1,2,4]): %d\n", slices.Compare(slice1, slice2))
fmt.Printf("Compare([1,2,3], [1,2,3]): %d\n", slices.Compare(slice1, slice1))
fmt.Printf("Compare([1,2,4], [1,2,3]): %d\n", slices.Compare(slice2, slice1))
fmt.Printf("Compare([1,2,3], [1,2,3,4]): %d\n", slices.Compare(slice1, slice3))
// section: example
}Output:
Compare([1,2,3], [1,2,4]): -1
Compare([1,2,3], [1,2,3]): 0
Compare([1,2,4], [1,2,3]): 1
Compare([1,2,3], [1,2,3,4]): -1Compare([1,2,3], [1,2,4]): -1
Compare([1,2,3], [1,2,3]): 0
Compare([1,2,4], [1,2,3]): 1
Compare([1,2,3], [1,2,3,4]): -1Contains
Contains reports if the provided value exists in the slice. This is useful to detect the presence of an element in a slice.
s := []string{"Apple", "Banana", "Orange"}
fmt.Println("Found Apple?", slices.Contains(s, "Apple"))
fmt.Println("Found Banana?", slices.Contains(s, "Banana"))
fmt.Println("Found Strawberry?", slices.Contains(s, "Strawberry"))package main
import (
"fmt"
"slices"
)
func main() {
// section: example
s := []string{"Apple", "Banana", "Orange"}
fmt.Println("Found Apple?", slices.Contains(s, "Apple"))
fmt.Println("Found Banana?", slices.Contains(s, "Banana"))
fmt.Println("Found Strawberry?", slices.Contains(s, "Strawberry"))
// section: example
}Output:
Found Apple? true
Found Banana? true
Found Strawberry? falseFound Apple? true
Found Banana? true
Found Strawberry? falseEqual
Equal reports whether two slices are equal: the same length and all elements are equal. If the lengths are different, Equal returns false.
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{1, 2, 3, 4, 5}
slice3 := []int{1, 2, 3, 4, 6}
fmt.Println("slice1 == slice2:", slices.Equal(slice1, slice2))
fmt.Println("slice1 == slice3:", slices.Equal(slice1, slice3))package main
import (
"fmt"
"slices"
)
func main() {
// section: example
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{1, 2, 3, 4, 5}
slice3 := []int{1, 2, 3, 4, 6}
fmt.Println("slice1 == slice2:", slices.Equal(slice1, slice2))
fmt.Println("slice1 == slice3:", slices.Equal(slice1, slice3))
// section: example
}Output:
slice1 == slice2: true
slice1 == slice3: falseslice1 == slice2: true
slice1 == slice3: falseIndex
Index returns the index of the first occurrence of the searched value in the slice. It will return -1 if the value is not present.
s := []string{"Apple", "Banana", "Orange"}
fmt.Println("Index for Apple: ", slices.Index(s, "Apple"))
fmt.Println("Index for Banana: ", slices.Index(s, "Banana"))
fmt.Println("Index for Strawberry: ", slices.Index(s, "Strawberry"))package main
import (
"fmt"
"slices"
)
func main() {
// section: example
s := []string{"Apple", "Banana", "Orange"}
fmt.Println("Index for Apple: ", slices.Index(s, "Apple"))
fmt.Println("Index for Banana: ", slices.Index(s, "Banana"))
fmt.Println("Index for Strawberry: ", slices.Index(s, "Strawberry"))
// section: example
}Output:
Index for Apple: 0
Index for Banana: 1
Index for Strawberry: -1Index for Apple: 0
Index for Banana: 1
Index for Strawberry: -1Insert
Insert inserts the value at the index provided. The slice will grow as needed to make room for the inserted values.
package main
import (
"fmt"
"slices"
)
func main() {
// section: example
s := []string{"Apple", "Banana", "Orange"}
s = slices.Insert(s, 2, "Grape")
fmt.Println(s)
// section: example
}Output:
[Apple Banana Grape Orange][Apple Banana Grape Orange]Grow
Grow increases the slice’s capacity, if necessary, to guarantee space for another n elements. This is useful when you know you’ll be appending many elements and want to avoid multiple reallocations.
numbers := []int{1, 2, 3}
fmt.Printf("Original: len=%d, cap=%d, slice=%v\n",
len(numbers), cap(numbers), numbers)
// Grow increases capacity by at least n elements
numbers = slices.Grow(numbers, 10)
fmt.Printf("After Grow(10): len=%d, cap=%d, slice=%v\n",
len(numbers), cap(numbers), numbers)
// This is useful when you know you'll be appending many elements
// It pre-allocates capacity to avoid multiple reallocationspackage main
import (
"fmt"
"slices"
)
func main() {
// section: example
numbers := []int{1, 2, 3}
fmt.Printf("Original: len=%d, cap=%d, slice=%v\n",
len(numbers), cap(numbers), numbers)
// Grow increases capacity by at least n elements
numbers = slices.Grow(numbers, 10)
fmt.Printf("After Grow(10): len=%d, cap=%d, slice=%v\n",
len(numbers), cap(numbers), numbers)
// This is useful when you know you'll be appending many elements
// It pre-allocates capacity to avoid multiple reallocations
// section: example
}Output:
Original: len=3, cap=3, slice=[1 2 3]
After Grow(10): len=3, cap=14, slice=[1 2 3]Original: len=3, cap=3, slice=[1 2 3]
After Grow(10): len=3, cap=14, slice=[1 2 3]Max
Max returns the max value in the slice. It will panic if the slice is empty. The slice does NOT need to be sorted for this function to work.
strings := []string{"Apple", "Orange", "Apple", "Banana"}
ints := []int{0, 1, 9, 1, 2, 0, 5, 7, 5}
fmt.Println("Max value for strings slice is", slices.Max(strings))
fmt.Println("Max value for ints slice is", slices.Max(ints))package main
import (
"fmt"
"slices"
)
func main() {
// section: example
strings := []string{"Apple", "Orange", "Apple", "Banana"}
ints := []int{0, 1, 9, 1, 2, 0, 5, 7, 5}
fmt.Println("Max value for strings slice is", slices.Max(strings))
fmt.Println("Max value for ints slice is", slices.Max(ints))
// section: example
}Output:
Max value for strings slice is Orange
Max value for ints slice is 9Max value for strings slice is Orange
Max value for ints slice is 9Min
Min returns the minimal value in the slice. It will panic if the slice is empty. The slice does NOT need to be sorted for this function to work.
strings := []string{"Apple", "Orange", "Apple", "Banana"}
ints := []int{0, 1, 9, 1, 2, 0, 5, 7, 5}
fmt.Println("Min value for strings slice is", slices.Min(strings))
fmt.Println("Min value for ints slice is", slices.Min(ints))package main
import (
"fmt"
"slices"
)
func main() {
// section: example
strings := []string{"Apple", "Orange", "Apple", "Banana"}
ints := []int{0, 1, 9, 1, 2, 0, 5, 7, 5}
fmt.Println("Min value for strings slice is", slices.Min(strings))
fmt.Println("Min value for ints slice is", slices.Min(ints))
// section: example
}Output:
Min value for strings slice is Apple
Min value for ints slice is 0Min value for strings slice is Apple
Min value for ints slice is 0Replace
Replace replaces the selected section of the slice with the provided values.
Note: You do not need to replace the with the exact number of elements. You can use less or more as necessary. This would allow you to replace 2 elements with 1 element, or 1 element with 3 elements, etc.
strings := []string{"Apple", "Orange", "Apple", "Banana"}
ints := []int{0, 1, 9, 1, 2, 0, 5, 7, 5}
strings = slices.Replace(strings, 0, 2, "Apple", "Grape")
ints = slices.Replace(ints, 0, 2, 9, 9)
fmt.Println(strings)
fmt.Println(ints)
// Replace and insert at the same time
strings = slices.Replace(strings, 0, 0, "Prune", "Pineapple")
ints = slices.Replace(ints, 0, 0, -1, -1)
fmt.Println(strings)
fmt.Println(ints)package main
import (
"fmt"
"slices"
)
func main() {
// section: example
strings := []string{"Apple", "Orange", "Apple", "Banana"}
ints := []int{0, 1, 9, 1, 2, 0, 5, 7, 5}
strings = slices.Replace(strings, 0, 2, "Apple", "Grape")
ints = slices.Replace(ints, 0, 2, 9, 9)
fmt.Println(strings)
fmt.Println(ints)
// Replace and insert at the same time
strings = slices.Replace(strings, 0, 0, "Prune", "Pineapple")
ints = slices.Replace(ints, 0, 0, -1, -1)
fmt.Println(strings)
fmt.Println(ints)
// section: example
}Output:
[Apple Grape Apple Banana]
[9 9 9 1 2 0 5 7 5]
[Prune Pineapple Apple Grape Apple Banana]
[-1 -1 9 9 9 1 2 0 5 7 5][Apple Grape Apple Banana]
[9 9 9 1 2 0 5 7 5]
[Prune Pineapple Apple Grape Apple Banana]
[-1 -1 9 9 9 1 2 0 5 7 5]Reverse
Reverse reverses the elements of the slice in place. This modifies the original slice.
numbers := []int{1, 2, 3, 4, 5}
words := []string{"Apple", "Banana", "Cherry"}
fmt.Println("Before reverse:")
fmt.Println(" numbers:", numbers)
fmt.Println(" words: ", words)
slices.Reverse(numbers)
slices.Reverse(words)
fmt.Println("\nAfter reverse:")
fmt.Println(" numbers:", numbers)
fmt.Println(" words: ", words)package main
import (
"fmt"
"slices"
)
func main() {
// section: example
numbers := []int{1, 2, 3, 4, 5}
words := []string{"Apple", "Banana", "Cherry"}
fmt.Println("Before reverse:")
fmt.Println(" numbers:", numbers)
fmt.Println(" words: ", words)
slices.Reverse(numbers)
slices.Reverse(words)
fmt.Println("\nAfter reverse:")
fmt.Println(" numbers:", numbers)
fmt.Println(" words: ", words)
// section: example
}Output:
Before reverse:
numbers: [1 2 3 4 5]
words: [Apple Banana Cherry]
After reverse:
numbers: [5 4 3 2 1]
words: [Cherry Banana Apple]Before reverse:
numbers: [1 2 3 4 5]
words: [Apple Banana Cherry]
After reverse:
numbers: [5 4 3 2 1]
words: [Cherry Banana Apple]Sort
Sort sorts a slice of any ordered type in ascending order. Probably one of the most useful functions added to the standard library since Go was released in 2012!
strings := []string{"Apple", "Orange", "Apple", "Banana"}
ints := []int{0, 1, 1, 2, 0, 5, 7, 5}
fmt.Println(strings)
fmt.Println(ints)
// Sort strings
slices.Sort(ints)
slices.Sort(strings)
fmt.Println(strings)
fmt.Println(ints)package main
import (
"fmt"
"slices"
)
func main() {
// section: example
strings := []string{"Apple", "Orange", "Apple", "Banana"}
ints := []int{0, 1, 1, 2, 0, 5, 7, 5}
fmt.Println(strings)
fmt.Println(ints)
// Sort strings
slices.Sort(ints)
slices.Sort(strings)
fmt.Println(strings)
fmt.Println(ints)
// section: example
}Output:
[Apple Orange Apple Banana]
[0 1 1 2 0 5 7 5]
[Apple Apple Banana Orange]
[0 0 1 1 2 5 5 7][Apple Orange Apple Banana]
[0 1 1 2 0 5 7 5]
[Apple Apple Banana Orange]
[0 0 1 1 2 5 5 7]Concat
Concat returns a new slice concatenating the passed in slices. This provides a cleaner alternative to chained append calls.
s1 := []string{"apple", "banana"}
s2 := []string{"cherry", "date"}
s3 := []string{"elderberry"}
// Before Go 1.21, you would need to chain append calls:
// combined := append(append(s1, s2...), s3...)
// With slices.Concat, this becomes cleaner:
combined := slices.Concat(s1, s2, s3)
fmt.Println("s1:", s1)
fmt.Println("s2:", s2)
fmt.Println("s3:", s3)
fmt.Println("combined:", combined)package main
import (
"fmt"
"slices"
)
func main() {
// section: example
s1 := []string{"apple", "banana"}
s2 := []string{"cherry", "date"}
s3 := []string{"elderberry"}
// Before Go 1.21, you would need to chain append calls:
// combined := append(append(s1, s2...), s3...)
// With slices.Concat, this becomes cleaner:
combined := slices.Concat(s1, s2, s3)
fmt.Println("s1:", s1)
fmt.Println("s2:", s2)
fmt.Println("s3:", s3)
fmt.Println("combined:", combined)
// section: example
}Output:
s1: [apple banana]
s2: [cherry date]
s3: [elderberry]
combined: [apple banana cherry date elderberry]s1: [apple banana]
s2: [cherry date]
s3: [elderberry]
combined: [apple banana cherry date elderberry]Added in Go 1.21
Repeat
Repeat returns a new slice that repeats the provided slice the given number of times.
pattern := []string{"Go", "is", "fun"}
// Repeat the pattern 3 times
repeated := slices.Repeat(pattern, 3)
fmt.Println("pattern:", pattern)
fmt.Println("repeated:", repeated)
// Useful for creating initialized slices
zeros := slices.Repeat([]int{0}, 5)
fmt.Println("zeros:", zeros)
// Or creating separator patterns
separator := slices.Repeat([]string{"-"}, 3)
fmt.Println("separator:", separator)package main
import (
"fmt"
"slices"
)
func main() {
// section: example
pattern := []string{"Go", "is", "fun"}
// Repeat the pattern 3 times
repeated := slices.Repeat(pattern, 3)
fmt.Println("pattern:", pattern)
fmt.Println("repeated:", repeated)
// Useful for creating initialized slices
zeros := slices.Repeat([]int{0}, 5)
fmt.Println("zeros:", zeros)
// Or creating separator patterns
separator := slices.Repeat([]string{"-"}, 3)
fmt.Println("separator:", separator)
// section: example
}Output:
pattern: [Go is fun]
repeated: [Go is fun Go is fun Go is fun]
zeros: [0 0 0 0 0]
separator: [- - -]pattern: [Go is fun]
repeated: [Go is fun Go is fun Go is fun]
zeros: [0 0 0 0 0]
separator: [- - -]Added in Go 1.23
Iterator Functions (Go 1.23+)
Go 1.23 introduced iterator support throughout the standard library, and the slices package gained several powerful iterator-based functions. These functions work with Go’s range-over-function feature and integrate with the iter package.
All
All returns an iterator over index-value pairs in the slice. This is similar to using range directly, but returns an iterator that can be passed to other iterator-consuming functions.
fruits := []string{"apple", "banana", "cherry"}
// slices.All returns an iterator over index-value pairs
// This is similar to range, but returns an iterator
fmt.Println("Using slices.All:")
for i, v := range slices.All(fruits) {
fmt.Printf(" index %d: %s\n", i, v)
}
// Useful when you need to pass an iterator to another function
// or when working with iterator-based APIspackage main
import (
"fmt"
"slices"
)
func main() {
// section: example
fruits := []string{"apple", "banana", "cherry"}
// slices.All returns an iterator over index-value pairs
// This is similar to range, but returns an iterator
fmt.Println("Using slices.All:")
for i, v := range slices.All(fruits) {
fmt.Printf(" index %d: %s\n", i, v)
}
// Useful when you need to pass an iterator to another function
// or when working with iterator-based APIs
// section: example
}Output:
Using slices.All:
index 0: apple
index 1: banana
index 2: cherryUsing slices.All:
index 0: apple
index 1: banana
index 2: cherryValues
Values returns an iterator over just the values in the slice, without indices. This is useful when you only care about the values and want to pass them to iterator-consuming functions.
numbers := []int{10, 20, 30, 40}
// slices.Values returns an iterator over values only (no indices)
fmt.Println("Using slices.Values:")
for v := range slices.Values(numbers) {
fmt.Printf(" value: %d\n", v)
}
// This is useful when you only care about values
// and want to pass them to iterator-consuming functionspackage main
import (
"fmt"
"slices"
)
func main() {
// section: example
numbers := []int{10, 20, 30, 40}
// slices.Values returns an iterator over values only (no indices)
fmt.Println("Using slices.Values:")
for v := range slices.Values(numbers) {
fmt.Printf(" value: %d\n", v)
}
// This is useful when you only care about values
// and want to pass them to iterator-consuming functions
// section: example
}Output:
Using slices.Values:
value: 10
value: 20
value: 30
value: 40Using slices.Values:
value: 10
value: 20
value: 30
value: 40Backward
Backward returns an iterator that yields index-value pairs in reverse order, from last to first. Unlike slices.Reverse, this doesn’t modify the original slice.
items := []string{"first", "second", "third", "fourth"}
// slices.Backward returns an iterator that yields
// index-value pairs in reverse order
fmt.Println("Using slices.Backward:")
for i, v := range slices.Backward(items) {
fmt.Printf(" index %d: %s\n", i, v)
}
// Unlike slices.Reverse, this doesn't modify the original slice
fmt.Println("Original slice unchanged:", items)package main
import (
"fmt"
"slices"
)
func main() {
// section: example
items := []string{"first", "second", "third", "fourth"}
// slices.Backward returns an iterator that yields
// index-value pairs in reverse order
fmt.Println("Using slices.Backward:")
for i, v := range slices.Backward(items) {
fmt.Printf(" index %d: %s\n", i, v)
}
// Unlike slices.Reverse, this doesn't modify the original slice
fmt.Println("Original slice unchanged:", items)
// section: example
}Output:
Using slices.Backward:
index 3: fourth
index 2: third
index 1: second
index 0: first
Original slice unchanged: [first second third fourth]Using slices.Backward:
index 3: fourth
index 2: third
index 1: second
index 0: first
Original slice unchanged: [first second third fourth]Collect
Collect collects values from an iterator into a new slice. This is the inverse of Values - it takes an iterator and produces a slice.
// slices.Collect collects values from an iterator into a slice
m := map[string]int{
"apple": 1,
"banana": 2,
"cherry": 3,
}
// Get all keys from a map as a slice using maps.Keys iterator
keys := slices.Collect(maps.Keys(m))
slices.Sort(keys) // Sort for consistent output
fmt.Println("Keys:", keys)
// Get all values from a map as a slice using maps.Values iterator
values := slices.Collect(maps.Values(m))
slices.Sort(values) // Sort for consistent output
fmt.Println("Values:", values)package main
import (
"fmt"
"maps"
"slices"
)
func main() {
// section: example
// slices.Collect collects values from an iterator into a slice
m := map[string]int{
"apple": 1,
"banana": 2,
"cherry": 3,
}
// Get all keys from a map as a slice using maps.Keys iterator
keys := slices.Collect(maps.Keys(m))
slices.Sort(keys) // Sort for consistent output
fmt.Println("Keys:", keys)
// Get all values from a map as a slice using maps.Values iterator
values := slices.Collect(maps.Values(m))
slices.Sort(values) // Sort for consistent output
fmt.Println("Values:", values)
// section: example
}Output:
Keys: [apple banana cherry]
Values: [1 2 3]Keys: [apple banana cherry]
Values: [1 2 3]AppendSeq
AppendSeq appends values from an iterator to an existing slice and returns the result. Unlike Collect, it preserves existing elements.
existing := []string{"existing1", "existing2"}
m := map[string]int{
"new1": 1,
"new2": 2,
}
// AppendSeq appends values from an iterator to an existing slice
combined := slices.AppendSeq(existing, maps.Keys(m))
slices.Sort(combined) // Sort for consistent output
fmt.Println("Combined:", combined)
// Unlike Collect, AppendSeq preserves existing elements
// This is useful when building up a slice incrementallypackage main
import (
"fmt"
"maps"
"slices"
)
func main() {
// section: example
existing := []string{"existing1", "existing2"}
m := map[string]int{
"new1": 1,
"new2": 2,
}
// AppendSeq appends values from an iterator to an existing slice
combined := slices.AppendSeq(existing, maps.Keys(m))
slices.Sort(combined) // Sort for consistent output
fmt.Println("Combined:", combined)
// Unlike Collect, AppendSeq preserves existing elements
// This is useful when building up a slice incrementally
// section: example
}Output:
Combined: [existing1 existing2 new1 new2]Combined: [existing1 existing2 new1 new2]Sorted
Sorted collects values from an iterator and returns them as a sorted slice. This combines Collect and Sort in a single operation.
m := map[string]int{
"cherry": 3,
"apple": 1,
"banana": 2,
}
// slices.Sorted collects values from an iterator and returns
// them as a sorted slice - combines Collect and Sort in one step
sortedKeys := slices.Sorted(maps.Keys(m))
fmt.Println("Sorted keys:", sortedKeys)
// Compare with doing it manually:
// keys := slices.Collect(maps.Keys(m))
// slices.Sort(keys)
// There's also SortedFunc for custom comparison
// and SortedStableFunc for stable sorting with custom comparisonpackage main
import (
"fmt"
"maps"
"slices"
)
func main() {
// section: example
m := map[string]int{
"cherry": 3,
"apple": 1,
"banana": 2,
}
// slices.Sorted collects values from an iterator and returns
// them as a sorted slice - combines Collect and Sort in one step
sortedKeys := slices.Sorted(maps.Keys(m))
fmt.Println("Sorted keys:", sortedKeys)
// Compare with doing it manually:
// keys := slices.Collect(maps.Keys(m))
// slices.Sort(keys)
// There's also SortedFunc for custom comparison
// and SortedStableFunc for stable sorting with custom comparison
// section: example
}Output:
Sorted keys: [apple banana cherry]Sorted keys: [apple banana cherry]Related functions: SortedFunc for custom comparison and SortedStableFunc for stable sorting with custom comparison.
Chunk
Chunk returns an iterator over consecutive sub-slices of up to n elements each. The last chunk may have fewer than n elements if the slice length isn’t evenly divisible.
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// slices.Chunk returns an iterator over contiguous sub-slices
// of up to n elements each
fmt.Println("Original:", numbers)
fmt.Println("Chunks of 3:")
for chunk := range slices.Chunk(numbers, 3) {
fmt.Printf(" %v\n", chunk)
}
// Useful for batch processing
fmt.Println("Chunks of 4:")
for chunk := range slices.Chunk(numbers, 4) {
fmt.Printf(" %v\n", chunk)
}package main
import (
"fmt"
"slices"
)
func main() {
// section: example
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// slices.Chunk returns an iterator over contiguous sub-slices
// of up to n elements each
fmt.Println("Original:", numbers)
fmt.Println("Chunks of 3:")
for chunk := range slices.Chunk(numbers, 3) {
fmt.Printf(" %v\n", chunk)
}
// Useful for batch processing
fmt.Println("Chunks of 4:")
for chunk := range slices.Chunk(numbers, 4) {
fmt.Printf(" %v\n", chunk)
}
// section: example
}Output:
Original: [1 2 3 4 5 6 7 8 9 10]
Chunks of 3:
[1 2 3]
[4 5 6]
[7 8 9]
[10]
Chunks of 4:
[1 2 3 4]
[5 6 7 8]
[9 10]Original: [1 2 3 4 5 6 7 8 9 10]
Chunks of 3:
[1 2 3]
[4 5 6]
[7 8 9]
[10]
Chunks of 4:
[1 2 3 4]
[5 6 7 8]
[9 10]Summary
As you can see, the slices package adds many useful functions that most Go developers will be using daily. Since Go 1.21, the package has continued to grow:
- Go 1.21: Added
Concatfor cleaner slice concatenation - Go 1.23: Added
Repeatfor repeating slices, plus iterator functions (All,Values,Backward,Collect,AppendSeq,Sorted,SortedFunc,SortedStableFunc,Chunk)
Below, I’ll show an example of something that previously would have required a lot of code (and somewhat hard to understand code as well). For this example, we will take a slice that has duplicate items and:
- Replace all occurrences of “snake” with “gopher”
- Sort the slice
- Compact the slice
- Clip the slice to free up any space (not necessary on a slice this size, but using it to show it would be useful on a larger scale operation).
Note: If this wasn’t an example, I would have sorted and compacted the slice first to decrease the amount of work that the Replace function had to do. But I wanted to make this a fun example to show off the slices package!
animals := []string{
"snake",
"guinea pig",
"elephant",
"deer",
"snake",
"dung beetle",
"okapi",
"badger",
"lovebird",
"coyote",
"camel",
"antelope",
"marten",
"rabbit",
"cougar",
"cow",
"finch",
"snake",
"reindeer",
"rat",
"moose",
"crocodile",
"snake",
"giraffe",
"gnu",
}
fmt.Printf("Original Slice:\nlen: %d, \ncap: %d, \nelements: %v\n", len(animals), cap(animals), animals)
i := slices.Index(animals, "snake")
for i > -1 {
animals = slices.Replace(animals, i, i+1, "gopher")
i = slices.Index(animals, "snake")
}
slices.Sort(animals)
animals = slices.Compact(animals)
animals = slices.Clip(animals)
fmt.Printf("Final Slice:\nlen: %d, \ncap: %d, \nelements: %v\n", len(animals), cap(animals), animals)package main
import (
"fmt"
"slices"
)
func main() {
// section: example
animals := []string{
"snake",
"guinea pig",
"elephant",
"deer",
"snake",
"dung beetle",
"okapi",
"badger",
"lovebird",
"coyote",
"camel",
"antelope",
"marten",
"rabbit",
"cougar",
"cow",
"finch",
"snake",
"reindeer",
"rat",
"moose",
"crocodile",
"snake",
"giraffe",
"gnu",
}
fmt.Printf("Original Slice:\nlen: %d, \ncap: %d, \nelements: %v\n", len(animals), cap(animals), animals)
i := slices.Index(animals, "snake")
for i > -1 {
animals = slices.Replace(animals, i, i+1, "gopher")
i = slices.Index(animals, "snake")
}
slices.Sort(animals)
animals = slices.Compact(animals)
animals = slices.Clip(animals)
fmt.Printf("Final Slice:\nlen: %d, \ncap: %d, \nelements: %v\n", len(animals), cap(animals), animals)
// section: example
}Output:
Original Slice:
len: 25,
cap: 25,
elements: [snake guinea pig elephant deer snake dung beetle okapi badger lovebird coyote camel antelope marten rabbit cougar cow finch snake reindeer rat moose crocodile snake giraffe gnu]
Final Slice:
len: 22,
cap: 22,
elements: [antelope badger camel cougar cow coyote crocodile deer dung beetle elephant finch giraffe gnu gopher guinea pig lovebird marten moose okapi rabbit rat reindeer]Original Slice:
len: 25,
cap: 25,
elements: [snake guinea pig elephant deer snake dung beetle okapi badger lovebird coyote camel antelope marten rabbit cougar cow finch snake reindeer rat moose crocodile snake giraffe gnu]
Final Slice:
len: 22,
cap: 22,
elements: [antelope badger camel cougar cow coyote crocodile deer dung beetle elephant finch giraffe gnu gopher guinea pig lovebird marten moose okapi rabbit rat reindeer]Performance Benchmarks
Understanding the performance characteristics of slice operations helps you write more efficient code. Pre-allocating capacity and choosing the right approach can significantly impact performance.
func main() {
fmt.Println("This package contains benchmark tests.")
fmt.Println("Run benchmarks with: go test -bench=.")
fmt.Println()
fmt.Println("Example commands:")
fmt.Println(" go test -bench=. -benchmem")
fmt.Println(" go test -bench=BenchmarkAppend -benchmem")
fmt.Println(" go test -bench=. -benchtime=10s")
fmt.Println()
fmt.Println("Key findings from benchmarks:")
fmt.Println()
fmt.Println("1. Pre-allocation with make([]T, 0, capacity) is significantly faster")
fmt.Println(" than growing a slice through repeated append operations.")
fmt.Println()
fmt.Println("2. Using make([]T, length) and direct indexing is fastest when you")
fmt.Println(" know the final size, avoiding append overhead entirely.")
fmt.Println()
fmt.Println("3. As slice size increases, the performance impact of not pre-allocating")
fmt.Println(" becomes more severe due to repeated memory reallocations.")
fmt.Println()
fmt.Println("4. For iteration, range loops are generally as fast or faster than")
fmt.Println(" index-based loops and are more idiomatic Go.")
fmt.Println()
fmt.Println("Best practices:")
fmt.Println("- Pre-allocate capacity when you know approximate final size")
fmt.Println("- Use make([]T, length) when size is exactly known")
fmt.Println("- Prefer range loops for readability without performance penalty")
}
package main
import "fmt"
// section: example
func main() {
fmt.Println("This package contains benchmark tests.")
fmt.Println("Run benchmarks with: go test -bench=.")
fmt.Println()
fmt.Println("Example commands:")
fmt.Println(" go test -bench=. -benchmem")
fmt.Println(" go test -bench=BenchmarkAppend -benchmem")
fmt.Println(" go test -bench=. -benchtime=10s")
fmt.Println()
fmt.Println("Key findings from benchmarks:")
fmt.Println()
fmt.Println("1. Pre-allocation with make([]T, 0, capacity) is significantly faster")
fmt.Println(" than growing a slice through repeated append operations.")
fmt.Println()
fmt.Println("2. Using make([]T, length) and direct indexing is fastest when you")
fmt.Println(" know the final size, avoiding append overhead entirely.")
fmt.Println()
fmt.Println("3. As slice size increases, the performance impact of not pre-allocating")
fmt.Println(" becomes more severe due to repeated memory reallocations.")
fmt.Println()
fmt.Println("4. For iteration, range loops are generally as fast or faster than")
fmt.Println(" index-based loops and are more idiomatic Go.")
fmt.Println()
fmt.Println("Best practices:")
fmt.Println("- Pre-allocate capacity when you know approximate final size")
fmt.Println("- Use make([]T, length) when size is exactly known")
fmt.Println("- Prefer range loops for readability without performance penalty")
}
// section: exampleRun the benchmarks to see the actual performance differences:
cd src/examples/benchmarks
go test -bench=. -benchmem
Key findings:
- Pre-allocating with
make([]T, 0, capacity)is significantly faster than growing through repeated appends - Using
make([]T, length)with direct indexing is fastest when size is known - The performance impact grows worse as slice size increases
- Range and index loops are optimized identically by the compiler through Bounds Check Elimination (BCE), resulting in equivalent performance. Choose
rangefor idiomatic, readable code
Slice Tricks
In addition to the slices package, there is also a wiki that includes many additional patterns that may be useful when using slices:
Exercise (15 Mins)
package main
import "fmt"
func main() {
parents := []string{"Carol", "Mike"}
kids := []string{"Marcia", "Jan", "Cindy", "Greg", "Peter", "Bobby"}
// TODO: Create a new slice called family by joining the parents and kids slice together
// TODO: Print out the value of `family`
// TODO: Print the length and capacity of the new slice.
// TODO: Create a new slice called "boys" by taking the last 3 elements of the slice you just made.
// HINT: slice[starting_index : (starting_index + length)], or use the shorthand version of slicing
// TODO: Print out the value of the new slice `boys` that you just created
// TODO: Fix the following bugs
extras := make([]string, 1, 0)
extras[1] = "Alice"
fmt.Println(extras)
}package main
import "fmt"
func main() {
parents := []string{"Carol", "Mike"}
kids := []string{"Marcia", "Jan", "Cindy", "Greg", "Peter", "Bobby"}
// TODO: Create a new slice called family by joining the parents and kids slice together
// TODO: Print out the value of `family`
// TODO: Print the length and capacity of the new slice.
// TODO: Create a new slice called "boys" by taking the last 3 elements of the slice you just made.
// HINT: slice[starting_index : (starting_index + length)], or use the shorthand version of slicing
// TODO: Print out the value of the new slice `boys` that you just created
// TODO: Fix the following bugs
extras := make([]string, 1, 0)
extras[1] = "Alice"
fmt.Println(extras)
}Solution
package main
import "fmt"
func main() {
parents := []string{"Carol", "Mike"}
kids := []string{"Marcia", "Jan", "Cindy", "Greg", "Peter", "Bobby"}
// TODO: Create a new slice called family by joining the parents and kids slice together
family := append(parents, kids...)
// TODO: Print out the value of `family`
fmt.Println(family)
// TODO: Print the length and capacity of the new slice.
fmt.Printf("len(family): %d, cap(family): %d\n", len(family), cap(family))
// TODO: Create a new slice called "boys" by taking the last 3 elements of the slice you just made.
// HINT: slice[starting_index : (starting_index + length)], or use the shorthand version of slicing
boys := family[len(family)-3:] // or boys := family[5:]
// TODO: Print out the value of the new slice `boys` that you just created
fmt.Println(boys)
// TODO: Fix the following bugs
extras := make([]string, 1, 1)
extras[0] = "Alice"
fmt.Println(extras)
}package main
import "fmt"
func main() {
parents := []string{"Carol", "Mike"}
kids := []string{"Marcia", "Jan", "Cindy", "Greg", "Peter", "Bobby"}
// TODO: Create a new slice called family by joining the parents and kids slice together
family := append(parents, kids...)
// TODO: Print out the value of `family`
fmt.Println(family)
// TODO: Print the length and capacity of the new slice.
fmt.Printf("len(family): %d, cap(family): %d\n", len(family), cap(family))
// TODO: Create a new slice called "boys" by taking the last 3 elements of the slice you just made.
// HINT: slice[starting_index : (starting_index + length)], or use the shorthand version of slicing
boys := family[len(family)-3:] // or boys := family[5:]
// TODO: Print out the value of the new slice `boys` that you just created
fmt.Println(boys)
// TODO: Fix the following bugs
extras := make([]string, 1, 1)
extras[0] = "Alice"
fmt.Println(extras)
}You can also use copy and potentially avoid allocations
with the following solution:
package main
import "fmt"
func main() {
parents := []string{"Carol", "Mike"}
kids := []string{"Marcia", "Jan", "Cindy", "Greg", "Peter", "Bobby"}
// TODO: Create a new slice called family by joining the parents and kids slice together
family := make([]string, len(parents)+len(kids))
copy(family, parents)
copy(family[len(parents):], kids)
// TODO: Print out the value of `family`
fmt.Println(family)
// TODO: Print the length and capacity of the new slice.
fmt.Printf("len(family): %d, cap(family): %d\n", len(family), cap(family))
// TODO: Create a new slice called "boys" by taking the last 3 elements of the slice you just made.
// HINT: slice[starting_index : (starting_index + length)], or use the shorthand version of slicing
boys := family[len(family)-3:] // or boys := family[5:]
// TODO: Print out the value of the new slice `boys` that you just created
fmt.Println(boys)
// TODO: Fix the following bugs
extras := make([]string, 1, 1)
extras[0] = "Alice"
fmt.Println(extras)
}package main
import "fmt"
func main() {
parents := []string{"Carol", "Mike"}
kids := []string{"Marcia", "Jan", "Cindy", "Greg", "Peter", "Bobby"}
// TODO: Create a new slice called family by joining the parents and kids slice together
family := make([]string, len(parents)+len(kids))
copy(family, parents)
copy(family[len(parents):], kids)
// TODO: Print out the value of `family`
fmt.Println(family)
// TODO: Print the length and capacity of the new slice.
fmt.Printf("len(family): %d, cap(family): %d\n", len(family), cap(family))
// TODO: Create a new slice called "boys" by taking the last 3 elements of the slice you just made.
// HINT: slice[starting_index : (starting_index + length)], or use the shorthand version of slicing
boys := family[len(family)-3:] // or boys := family[5:]
// TODO: Print out the value of the new slice `boys` that you just created
fmt.Println(boys)
// TODO: Fix the following bugs
extras := make([]string, 1, 1)
extras[0] = "Alice"
fmt.Println(extras)
}