Sometimes the order stored in a slice needs to change, not just the order in which code visits its elements. A queue may need newest-first presentation, a collected path may need to run from origin to destination, or a stack snapshot may need its top element first. slices.Reverse handles that edit directly.

The key detail is mutation. slices.Reverse rearranges the existing slice in place. It doesn’t return a replacement slice, and code sharing the same backing array can observe the new order.

Reverse a slice with one call

For a slice of any element type, call slices.Reverse with the slice itself:

package main

import (
    "fmt"
    "slices"
)

func main() {
    states := []string{"queued", "running", "done"}

    slices.Reverse(states)

    fmt.Println(states)
}

The output is:

[done running queued]

There is no assignment on the call. The function’s signature returns no value:

func Reverse[S ~[]E, E any](s S)

That shape communicates the operation clearly: the supplied slice is the object being changed.

In-place reversal changes element positions

A slice describes a view over an underlying array. Reversing the slice swaps elements from opposite ends, moving inward until the middle is reached. For an odd-length slice, the center element stays in the center.

Given this input:

index:  0  1  2  3  4
value:  A  B  C  D  E

reversal produces:

index:  0  1  2  3  4
value:  E  D  C  B  A

The length and capacity don’t change. What changes is which value occupies each position.

Calling the function twice restores the original order:

numbers := []int{1, 2, 3, 4}

slices.Reverse(numbers)
slices.Reverse(numbers)

fmt.Println(numbers) // [1 2 3 4]

This can be useful when a function temporarily needs the opposite order, but reversing twice around a large block of work can make mutation harder to track. A reverse iterator may express temporary traversal more cleanly.

Watch for slices that share storage

The most common surprise isn’t the reversal itself. It’s aliasing.

Two slice values can refer to the same backing array. If one view is reversed, another overlapping view can see those swaps too:

records := []string{"a", "b", "c"}
alias := records

slices.Reverse(records)

fmt.Println(records) // [c b a]
fmt.Println(alias)   // [c b a]

Assigning a slice to another variable copies the slice header, not all of its elements. Both variables still refer to the same storage in this example.

If the caller’s slice must remain untouched, clone before reversing:

reversed := slices.Clone(records)
slices.Reverse(reversed)

Now reversed has separate top-level element storage. Keep shallow-copy semantics in mind: if the elements themselves contain pointers, maps, slices, or other references, cloning the outer slice doesn’t recursively copy the referenced data.

Empty and single-element slices need no special branch

Reversing an empty slice has nothing to swap. The same is true for a one-element slice. Both cases can go through slices.Reverse without a separate length check.

A nil slice also remains nil:

var values []int
slices.Reverse(values)

fmt.Println(values == nil) // true

This makes the function convenient in code paths where an empty result is ordinary. A guard such as if len(values) > 1 is usually unnecessary unless surrounding work needs that condition for another reason.

Reverse stored order or only traversal order

Reverse and Backward solve related but different problems.

Use slices.Reverse when later code should observe the slice in the opposite stored order. After the call, indexing, ranging, serialization, and later function calls all see the rearranged elements.

Use slices.Backward when the slice should stay unchanged and only one traversal needs to move from the end toward the start:

for i, value := range slices.Backward(records) {
    fmt.Println(i, value)
}

That distinction matters in shared code. Mutating a collection merely to print it in reverse order introduces a state change that callers may not expect. In that case, reverse traversal is the narrower operation.

Cloning plus Reverse is another option when a separate reversed slice is genuinely needed:

reversed := slices.Clone(records)
slices.Reverse(reversed)

It costs storage for the copy, but it gives the new order independent top-level storage and leaves the source sequence intact.

Common mistakes around reversal

One mistake is expecting a returned slice:

// Incorrect: Reverse returns no value.
reversed := slices.Reverse(values)

Call the function directly instead. If a separate result is required, clone first and reverse the clone.

Another mistake is assuming that assigning the slice to a second variable creates an independent copy. It doesn’t. Use slices.Clone when independent element storage is part of the requirement.

A subtler issue appears when only a subslice is reversed. The operation affects exactly the elements visible through that subslice, but those elements still belong to the shared backing array:

values := []int{1, 2, 3, 4, 5}

slices.Reverse(values[1:4])

fmt.Println(values) // [1 4 3 2 5]

This is useful for localized edits, as long as the shared-storage effect is intentional.

Use slices.Reverse when mutation matches the requirement

slices.Reverse is a good fit when the stored sequence itself should be reversed and in-place mutation is acceptable. It works with any slice element type, requires no comparator, and handles empty inputs without special casing.

Before calling it, check one design point: should other code see the new order? If yes, reverse the slice directly. If the source order must remain intact, use reverse traversal or clone the slice before mutation. That small ownership decision prevents most surprises around an otherwise straightforward operation.