Reverse traversal does not require reversing a slice. Since Go 1.23, slices.Backward exposes the existing elements as an iterator that emits index-value pairs from the last element toward index zero.
That distinction matters when element order must stay intact. slices.Reverse changes the slice in place, while a hand-written descending loop couples traversal to index arithmetic. slices.Backward expresses reverse iteration without changing the source.
The iterator keeps original indexes
The function has this signature:
func Backward[Slice ~[]E, E any](s Slice) iter.Seq2[int, E]The first yielded value is the original index and the second is the element at that index. For a four-element slice, iteration emits indexes 3, 2, 1, then 0.
package main
import (
"fmt"
"slices"
)
func main() {
states := []string{"queued", "running", "flushing", "done"}
for i, state := range slices.Backward(states) {
fmt.Printf("%d: %s\n", i, state)
}
}The output is:
3: done
2: flushing
1: running
0: queuedNo translated reverse index is introduced. Code can use i directly with the original slice or with another data structure aligned to the same indexes.
An empty slice produces no values. A one-element slice produces the pair for index zero once.
Reverse traversal and reversal are different operations
slices.Reverse rearranges elements in place. That is appropriate when the slice itself must adopt the opposite order, but mutation is unnecessary when only the visitation order needs to run backward.
values := []int{10, 20, 30}
slices.Reverse(values)
fmt.Println(values)
// [30 20 10]With slices.Backward, the source order remains unchanged:
values := []int{10, 20, 30}
for _, value := range slices.Backward(values) {
fmt.Println(value)
}
fmt.Println(values)
// [10 20 30]This also avoids making a copy solely to protect the source from an in-place reversal. The iterator changes traversal order, not storage layout.
Descending index arithmetic moves into the library call
Before iterator support, the conventional reverse loop was compact but mechanical:
for i := len(values) - 1; i >= 0; i-- {
use(i, values[i])
}That form is valid. It also makes the loop responsible for computing the starting index, maintaining the descending condition, and indexing the slice on each pass.
The iterator form keeps the same original index semantics:
for i, value := range slices.Backward(values) {
use(i, value)
}The difference is primarily expression and composition. slices.Backward returns iter.Seq2[int, E], so it can be passed to code that accepts a two-value sequence rather than being tied to one range loop.
Early termination does not touch remaining elements
Range over an iterator supports break. When the caller has found the needed suffix element, traversal can stop without visiting earlier entries.
func lastNonEmpty(values []string) (int, string, bool) {
for i, value := range slices.Backward(values) {
if value != "" {
return i, value, true
}
}
return 0, "", false
}This performs a backward search while preserving the index from the original slice. It does not allocate a reversed copy and does not reorder values.
The same property fits data structures where recent entries tend to be near the end and the first matching entry encountered from that end is sufficient.
Yielded values follow normal range value semantics
The iterator yields an element value, not a pointer to the slice slot. Assigning to the range variable does not replace the source element.
values := []int{1, 2, 3}
for _, value := range slices.Backward(values) {
value = 0
_ = value
}
fmt.Println(values)
// [1 2 3]When mutation is intended, the yielded index provides direct access to the source slot:
for i := range slices.Backward(values) {
values[i] *= 2
}For element types that already contain references, such as pointers, maps, or slices, copying the element value retains the usual reference semantics of those types. Backward does not add a copy of referenced data.
Iterator traversal does not create a snapshot
slices.Backward describes iteration over a slice; it does not promise an immutable snapshot of its elements. Code should treat structural changes to the underlying slice during traversal with the same care as other iteration over shared storage.
In particular, reverse traversal is not a synchronization mechanism. If another goroutine can write the same elements concurrently, the program still needs synchronization appropriate to those accesses.
The API is narrow: it supplies descending index-value iteration. Ownership, mutation policy, and concurrent access remain properties of the surrounding code.
slices.Backward is most useful when reverse order is temporary rather than a new representation of the data. It keeps original indexes available, leaves the slice order untouched, and fits the standard iterator model introduced alongside Go’s range-over-function support.