Walking a slice from the end used to mean writing the index loop yourself. That works, but the loop mechanics can distract from the actual job, especially when you need both the original index and the value. Since Go 1.23, slices.Backward provides that traversal directly.
It returns an iterator. The slice stays in its original order, no reversed copy is created, and the indexes you receive are the real indexes from the source slice.
What slices.Backward returns
The function has this signature:
func Backward[Slice ~[]E, E any](s Slice) iter.Seq2[int, E]iter.Seq2 yields two values on each step. For slices.Backward, those values are the index and element. A four-element slice therefore yields indexes 3, 2, 1, then 0.
package main
import (
"fmt"
"slices"
)
func main() {
steps := []string{"parse", "compile", "test", "package"}
for i, step := range slices.Backward(steps) {
fmt.Printf("%d: %s\n", i, step)
}
}The output is:
3: package
2: test
1: compile
0: parseThose descending indexes are often the main reason to prefer Backward over creating a reversed copy. If an error refers to position 2, for example, you don’t have to translate an index from a second slice back to the original data.
Reverse traversal is not the same as reversing a slice
slices.Backward changes traversal order, not storage order. This distinction matters when other code still uses the same slice.
package main
import (
"fmt"
"slices"
)
func main() {
values := []int{10, 20, 30}
for _, value := range slices.Backward(values) {
fmt.Println(value)
}
fmt.Println(values) // [10 20 30]
}If you call slices.Reverse(values) instead, the elements in values are rearranged in place. That’s useful when later code should see the reversed order. It is a different operation from simply visiting the existing elements from right to left.
Building a reversed copy is another option, but it introduces a new slice and a copy step. That can be justified when you need to keep the reversed collection around. For a one-pass scan, slices.Backward states the intent without materializing another collection.
Keep the original index when searching from the end
Reverse scans often show up when the most recent or last matching item wins. A manual loop is familiar:
for i := len(events) - 1; i >= 0; i-- {
if events[i].ID == target {
return i
}
}With slices.Backward, the traversal rule moves out of the loop body:
package main
import (
"fmt"
"slices"
)
type Event struct {
ID string
Data string
}
func lastIndex(events []Event, target string) int {
for i, event := range slices.Backward(events) {
if event.ID == target {
return i
}
}
return -1
}
func main() {
events := []Event{
{ID: "job-7", Data: "queued"},
{ID: "job-2", Data: "running"},
{ID: "job-7", Data: "done"},
}
fmt.Println(lastIndex(events, "job-7")) // 2
}The first match encountered by the backward traversal is the last matching element in normal slice order. Because the iterator yields the source index, the function can return 2 directly.
This pattern is a good fit for histories, fallback chains, stacks represented as slices, and other data where the newest relevant entry tends to be near the end.
Breaking early stops the traversal
A range loop over slices.Backward can use break just like other range loops. You don’t need to consume the rest of the iterator.
for i, item := range slices.Backward(items) {
if item.Valid {
fmt.Println("last valid item is at", i)
break
}
}That makes backward traversal useful for searches where scanning the whole slice would be unnecessary. The code starts at the end and stops as soon as the condition is satisfied.
There is also no special case needed for an empty slice. slices.Backward simply yields no values, so the loop body doesn’t run. A nil slice behaves the same way for traversal purposes.
Avoid the unsigned-index trap in manual backward loops
One reason reverse loops deserve care is that the common signed-integer pattern doesn’t translate safely to unsigned indexes.
This loop is fine because i is an int:
for i := len(values) - 1; i >= 0; i-- {
use(values[i])
}Trying to recreate the same condition with an unsigned counter is dangerous because an unsigned value can’t become negative. After reaching zero, decrementing wraps to a large value instead of making the loop condition false.
slices.Backward removes that bookkeeping from the call site. The yielded index is an int, matching ordinary Go slice indexing, and empty slices require no separate guard.
This isn’t a reason to ban manual reverse loops. They remain useful when the index progression itself is part of the algorithm. But when the requirement is simply “visit this slice from the end,” using the library operation leaves less boundary logic to inspect.
Remember that Backward is an iterator
slices.Backward returns an iter.Seq2; it doesn’t return a slice. That means it fits naturally in a range loop, but it isn’t a reversed collection you can index later.
If downstream code genuinely needs stored values in reverse order, choose a representation that matches that need. You might copy the slice and call slices.Reverse on the copy, for example. Converting an iterator into a collection only to simulate indexing usually makes the code more complicated than choosing the collection up front.
The iterator form also means you can ignore the index when you don’t need it:
for _, value := range slices.Backward(values) {
process(value)
}Keep the index only when it carries useful information. This makes a reverse scan read much like a normal forward range loop.
Use slices.Backward when the direction is the operation
slices.Backward is a small API, but it captures a common intent cleanly: traverse an existing slice from its last element to its first while preserving source indexes. It doesn’t mutate the slice and doesn’t require a reversed copy.
Use it when the direction of the scan is what matters, especially for last-match searches or newest-first processing. If later code needs the data itself rearranged, slices.Reverse or an explicit reversed copy is the better tool. Choosing between those operations based on what must change—iteration order or the collection—keeps the code straightforward.