Retrieving Single Elements
This lesson shows how to get one element out of a slice: by its position, by a condition, or at random. It also shows how to check whether an element exists at all.
Go has no null. When a lookup can fail, a Go function returns two values: the element and a bool called ok. This is the same value, ok pattern you know from maps.
Reading a position that does not exist, such as words[5] in a slice of 5 elements, stops the program with a panic (a runtime error). So we always check len() first.
Other languages have many ready-made functions for this. Go keeps the language small: you use square brackets, the slices package, or a small helper function you write once.
Retrieve by Position
Every element in a slice has a position called an index. The first index is 0, so the last one is len(words)-1.
To read an element by position, write the index inside square brackets: words[3]. This is Go's way of reading the element at a position, the first one, or the last one.
Go has no elementAtOrNull() or elementAtOrElse(). A small generic helper checks the index against len() and returns either value, ok or the result of a fallback function. This way an out-of-range index cannot panic.
Element at a position
words[3] reads the element at index 3. Since counting starts at 0, that is the fourth word.
Output: Go program and captured output
package main
import "fmt"
func main() {
words := []string{"one", "two", "three", "four", "five"}
// Positions start at 0, so index 3 is the fourth element.
fmt.Println(words[3])
}
First and last element
The first element is words[0]. The last one is words[len(words)-1], because the length is 5 but the last index is 4.
Output: Go program and captured output
package main
import "fmt"
func main() {
words := []string{"one", "two", "three", "four", "five"}
// The first element is at index 0, the last one at len - 1.
fmt.Println(words[0])
fmt.Println(words[len(words)-1])
}
Element at a position, or ok == false
ElementAt checks the index against len(). Index 1 is in range, so it returns "two" and true. Index 5 is out of range, so it returns the zero value "" and false instead of panicking.
Output: Go program and captured output
package main
import "fmt"
// ElementAt returns the element at index i and true, or the zero value and false.
func ElementAt[T any](items []T, i int) (T, bool) {
if i < 0 || i >= len(items) {
var zero T
return zero, false
}
return items[i], true
}
func main() {
words := []string{"one", "two", "three", "four", "five"}
v1, ok1 := ElementAt(words, 1)
fmt.Printf("%q %v\n", v1, ok1)
// Index 5 does not exist: no panic, just ok == false.
v2, ok2 := ElementAt(words, 5)
fmt.Printf("%q %v\n", v2, ok2)
}
Element at a position, or a fallback
ElementAtOrElse takes a fallback function. The function receives the index, so it can build a message like "The value for index 5 is undefined".
Output: Go program and captured output
package main
import "fmt"
// ElementAtOrElse returns the element at index i, or the result of fallback(i).
func ElementAtOrElse[T any](items []T, i int, fallback func(int) T) T {
if i < 0 || i >= len(items) {
return fallback(i)
}
return items[i]
}
func main() {
words := []string{"one", "two", "three", "four", "five"}
fmt.Println(ElementAtOrElse(words, 5, func(i int) string {
return fmt.Sprintf("The value for index %d is undefined", i)
}))
}
Retrieve by Condition
Often you do not know the position. You know a condition instead: the first word longer than 3 letters, the last word that starts with "f". The condition is a function that takes one element and returns true or false.
For the first match, the slices package has slices.IndexFunc(). It returns the index of the first element that passes the test, or -1 when nothing does. You then read the element with that index.
There is no last-match function in slices. A for loop that counts down from len(words)-1 finds the last match: the first hit while walking backwards is the last one in the slice.
Go has no ready-made find functions beyond that. Small generic helpers Find and FindLast do the job and return value, ok, so "nothing matched" is just ok == false.
First match
slices.IndexFunc() gives the index of the first word with len(s) > 3. That word is "three".
Output: Go program and captured output
package main
import (
"fmt"
"slices"
)
func main() {
words := []string{"one", "two", "three", "four", "five"}
// IndexFunc gives the index of the first element that passes the test.
i := slices.IndexFunc(words, func(s string) bool { return len(s) > 3 })
fmt.Println(words[i])
}
Last match
The loop starts at the last index and moves down. The first word it meets that starts with "f" is "five", so the loop prints it and stops with break.
Output: Go program and captured output
package main
import (
"fmt"
"strings"
)
func main() {
words := []string{"one", "two", "three", "four", "five"}
// Walk from the end so the first hit is the last match.
for i := len(words) - 1; i >= 0; i-- {
if strings.HasPrefix(words[i], "f") {
fmt.Println(words[i])
break
}
}
}
First match, or ok == false
No word is longer than 6 letters, so Find returns ok == false and the program prints "not found". In the numbers slice the first even number is 2.
Output: Go program and captured output
package main
import "fmt"
// Find returns the first element that passes the test and true, or the zero value and false.
func Find[T any](items []T, match func(T) bool) (T, bool) {
for _, v := range items {
if match(v) {
return v, true
}
}
var zero T
return zero, false
}
func main() {
words := []string{"one", "two", "three", "four", "five"}
numbers := []int{1, 2, 3, 4}
if w, ok := Find(words, func(s string) bool { return len(s) > 6 }); ok {
fmt.Println(w)
} else {
fmt.Println("not found")
}
if n, ok := Find(numbers, func(n int) bool { return n%2 == 0 }); ok {
fmt.Println(n)
}
}
Last match, or ok == false
FindLast walks the numbers backwards. The last even number is 4, and it is found right away.
Output: Go program and captured output
package main
import "fmt"
// FindLast walks backwards and returns the last element that passes the test.
func FindLast[T any](items []T, match func(T) bool) (T, bool) {
for i := len(items) - 1; i >= 0; i-- {
if match(items[i]) {
return items[i], true
}
}
var zero T
return zero, false
}
func main() {
numbers := []int{1, 2, 3, 4}
if n, ok := FindLast(numbers, func(n int) bool { return n%2 == 0 }); ok {
fmt.Println(n)
}
}
Retrieve with Selector
Sometimes you want to change each element first and then take the first result that fits.
Go has no such function. A for loop does it: convert each element, test the result, and break at the first good one. The slice here has type []any, so it can hold a number, a string and a bool at the same time. fmt.Sprint() turns any value into text.
First converted value that fits
Each value is turned into text with fmt.Sprint(). 0 becomes "0", which is too short. "true" has 4 characters, so it is printed and the loop stops.
Output: Go program and captured output
package main
import "fmt"
func main() {
list := []any{0, "true", false}
// Turn each value into text and stop at the first text with 4 or more characters.
for _, v := range list {
s := fmt.Sprint(v)
if len(s) >= 4 {
fmt.Println(s)
break
}
}
}
Random Element
To pick a random element, pick a random index. The math/rand package gives random numbers; r.Intn(n) returns a number from 0 up to n-1, which is exactly the range of valid indexes.
Go has no random() method on slices, so the index goes into square brackets as usual.
A random source made with rand.NewSource(42) starts from a fixed seed. With the same seed the program picks the same element every time, which makes the output predictable. Leave out the seed when you want a different result on each run.
Pick a random element
With seed 42 the source picks index 1, so the program prints 2. Change the seed and a different number can come out.
Output: Go program and captured output
package main
import (
"fmt"
"math/rand"
)
func main() {
numbers := []int{1, 2, 3, 4}
// A fixed seed makes the "random" choice the same on every run.
r := rand.New(rand.NewSource(42))
i := r.Intn(len(numbers))
fmt.Println(numbers[i])
}
Check Element Existence
To check whether a value is in a slice, use slices.Contains(). It returns true or false.
Go has no containsAll(). A small generic helper loops over the wanted values and calls slices.Contains() for each one. It returns false as soon as one value is missing.
Go also has no isEmpty() or isNotEmpty(). Compare the length: len(s) == 0 means empty, len(s) > 0 means not empty.
Contains one value
"four" is in the slice, so the first check is true. "zero" is not, so the second check is false.
Output: Go program and captured output
package main
import (
"fmt"
"slices"
)
func main() {
words := []string{"one", "two", "three", "four", "five"}
fmt.Println(slices.Contains(words, "four"))
fmt.Println(slices.Contains(words, "zero"))
}
Contains all values
Both "four" and "one" are in the slice, so ContainsAll returns true. "zero" is missing, so the second call returns false.
Output: Go program and captured output
package main
import (
"fmt"
"slices"
)
// ContainsAll is true only when every wanted value is in items.
func ContainsAll[T comparable](items []T, wanted []T) bool {
for _, w := range wanted {
if !slices.Contains(items, w) {
return false
}
}
return true
}
func main() {
words := []string{"one", "two", "three", "four", "five"}
fmt.Println(ContainsAll(words, []string{"four", "one"}))
fmt.Println(ContainsAll(words, []string{"one", "zero"}))
}
Empty or not empty
empty has length 0, so len(empty) == 0 is true. words has 5 elements, so len(words) > 0 is also true.
Output: Go program and captured output
package main
import "fmt"
func main() {
words := []string{"one", "two", "three", "four", "five"}
empty := []string{}
// A slice is empty when its length is 0.
fmt.Println(len(empty) == 0)
fmt.Println(len(words) > 0)
}