skip to content

What do Go's `slices.Values` and `slices.All` return, and when would you need them?

level: middleimportance: should knowfreq 38%

answer

  1. one gives elements, one gives pairs
  2. range over a slice already works
  3. you need a value, not a language form
  4. iter.Seq[E] versus iter.Seq2[int, E]
  5. adapters for iterator-shaped parameters

basics

~20 s

slices.Values(s) returns an iter.Seq of the elements; slices.All(s) returns an iter.Seq2 of index and element pairs. Both adapt a slice into a sequence value so it can be passed to code whose parameter type is an iterator.

solid answer

~40 s

`slices.Values(s)` returns `iter.Seq[E]` — the elements, one at a time. `slices.All(s)` returns `iter.Seq2[int, E]` — index and element pairs, the same pairs a plain `for i, v := range s` gives you. There is also `slices.Backward(s)`, an `iter.Seq2[int, E]` walking from the last index down. You do not need any of them to loop over a slice; `range` already handles that. You need them the moment something wants a *sequence value*: a function parameter declared `iter.Seq[E]`, a helper such as `maps.Collect` or `maps.Insert` that consumes an `iter.Seq2`, or your own pipeline stage that should work over any source. A slice is not a function value, so it cannot be passed where `iter.Seq[E]` is expected — these two helpers are the adapters that close that gap.

code

go · 16 lines
go
func sum(seq iter.Seq[int]) int {
	total := 0
	for v := range seq {
		total += v
	}
	return total
}

nums := []int{3, 1, 2}

// sum(nums) does not compile: []int is not a func value.
n := sum(slices.Values(nums)) // n == 6

// slices.All yields index/value pairs, so it feeds an iter.Seq2 consumer.
byIndex := maps.Collect(slices.All(nums)) // map[int]int{0: 3, 1: 1, 2: 2}
_, _ = n, byIndex

go deeper

for a junior

Remember the two shapes: slices.Values gives elements, slices.All gives index and element. Know that ordinary looping over a slice does not need either of them.

for a middle

Explain that both return function values of type iter.Seq or iter.Seq2, that nothing is walked until something ranges them, and give a concrete case where a slice must be adapted because the parameter type is an iterator.

for a senior

Demonstrate the composition argument: one consumer written against iter.Seq serves slices, maps and streamed sources, and you keep the adapters at the call site. Flag the restartability difference between a slice adapter and an unknown sequence.

for a principal

Own the API decision of whether your package's functions take a slice or a sequence: sequences compose and defer allocation, slices are simpler, testable with literals and usable by callers on older Go releases.

## Two directions, two sets of helpers Go 1.23's iterator helpers come in matched pairs. One direction turns a *sequence into a container*: `slices.Collect`, `slices.Sorted`, `slices.AppendSeq`, `maps.Collect`, `maps.Insert`. The other turns a *container into a sequence*: `slices.Values`, `slices.All`, `slices.Backward`, `maps.Keys`, `maps.Values`, `maps.All`. `slices.Values` and `slices.All` are the slice side of that second direction. ```go func Values[S ~[]E, E any](s S) iter.Seq[E] func All[S ~[]E, E any](s S) iter.Seq2[int, E] ``` `iter.Seq[E]` is `func(yield func(E) bool)` and `iter.Seq2[K, V]` is `func(yield func(K, V) bool)`. So both return **function values**. Calling `slices.Values(s)` walks nothing; it just wraps the slice in a closure that will walk it when driven. ## Why they exist when `range` already works For a plain loop they are pure overhead: ```go for i, v := range s // idiomatic for i, v := range slices.All(s) // same pairs, extra indirection ``` The difference is that `range s` is a **language form** — the compiler special-cases slices — while `slices.All(s)` produces a **value** you can store in a variable, pass as an argument, return from a function, or hand to a generic helper. That value is what makes composition possible. A function declared as ```go func sum(seq iter.Seq[int]) int ``` accepts a slice's elements, a map's values, rows streamed from a file, or a filtered view of any of those — all through one parameter type. Without the adapter you would have to write a `[]int` overload and an iterator overload of everything. ## Which one to reach for - **`slices.Values`** when the consumer cares only about elements. This is the common case: `slices.Sorted(slices.Values(s))` sorts a copy without touching the original, and any `iter.Seq[E]` parameter takes it. - **`slices.All`** when the position matters, or when the consumer wants an `iter.Seq2`. `maps.Collect(slices.All(s))` builds a `map[int]E` keyed by position; `maps.Insert(m, slices.All(s))` writes those pairs into an existing map. - **`slices.Backward`** for a reverse walk with indexes, which is otherwise a fiddly downward `for` loop. A detail worth stating in an interview: because these adapters re-read the slice from the start each time the returned function is invoked, they are **restartable** — you can range the same `slices.Values(s)` value twice and get the same elements twice. An arbitrary `iter.Seq` you were handed carries no such promise, because whoever wrote it may have been draining a network connection or a channel. That asymmetry is why code that must traverse an unknown sequence more than once collects it into a slice first, then ranges the slice. Another detail: the adapter closes over the slice header, not a copy of the elements. Mutating `s[0]` between two traversals of the same `slices.Values(s)` value shows the new element on the second pass, and appending to `s` afterwards may or may not be visible depending on whether `append` reallocated. Treat the sequence as a live view of the slice you handed it, not a snapshot. ## The type constraint Both take `S ~[]E`, with the tilde meaning "any type whose underlying type is `[]E`". That is what lets you pass a named slice type such as `type IDs []int` straight in, without a conversion. `E` itself is unconstrained — `any` — so these work for element types that are not comparable or ordered; only the collectors that sort, like `slices.Sorted`, add a `cmp.Ordered` constraint. ## The mental model Think of `iter.Seq` as the interface every collection can present, and of `slices.Values` / `slices.All` / `maps.Keys` / `maps.All` as the free adapters that let a concrete container present it. Write your consumers against the sequence type, keep the adapters at the call site, and collect back into a slice or map only where you actually need a container again.

  • If range over a slice already works, what does slices.All buy you?
    `range s` is a language form the compiler special-cases; `slices.All(s)` is a *value*. You can store it, pass it as an `iter.Seq2[int, E]` argument, return it, or hand it to `maps.Collect`. That is what lets one consumer accept a slice, a map or a streamed source through a single parameter type instead of an overload per container.
  • Can you range over the same slices.Values(s) value twice?
    Yes — it re-reads the slice from the start on every invocation, so it is restartable. That is not true of sequences in general: one built over a network stream or a channel may be single-use. If you were handed an unknown `iter.Seq` and need two passes, collect it into a slice first with `slices.Collect` and range that.
  • What is slices.Backward, and how does it differ from slices.All?
    Both return `iter.Seq2[int, E]` over a slice, but `slices.Backward` yields the elements from the last index down to zero, while `slices.All` goes from zero up. The indexes it yields are the real positions in the slice, not a reversed count, so `s[i]` is still the yielded element.

saying these in an interview costs you the question

  • Says slices.Values copies the elements into a new slice
  • Thinks a []int can be passed where iter.Seq[int] is expected
  • Uses slices.All for an ordinary loop over a slice
  • Confuses slices.Values with the old maps helper returning a slice
  • Expects slices.All to yield element and a found bool