Reversing a Go slice changes element order without requiring a second slice. The standard library’s slices.Reverse function performs that operation in place, so the slice header keeps referring to the same backing storage while pairs of elements are exchanged from the ends toward the center.

That in-place behavior is the main property to account for. A call can affect other slices that share the same backing array, and no returned slice exists to make the mutation visually obvious at the call site.

Reverse mutates the supplied slice

The function has a compact signature:

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

It accepts any slice type, including named slice types, and returns nothing. For a simple integer slice:

package main

import (
	"fmt"
	"slices"
)

func main() {
	values := []int{10, 20, 30, 40, 50}

	slices.Reverse(values)

	fmt.Println(values)
}

The resulting order is:

[50 40 30 20 10]

Conceptually, the operation exchanges the first and last elements, then the second and second-to-last elements, continuing until the indices meet or cross. For an odd-length slice, the center element remains in its existing slot.

The function changes neither length nor capacity. It also does not sort values or inspect their contents. Reversal is purely positional.

Shared backing arrays expose the mutation

A slice is a descriptor over an underlying array. Reversing through one slice therefore changes the array elements visible through any overlapping slice.

package main

import (
	"fmt"
	"slices"
)

func main() {
	all := []string{"a", "b", "c", "d", "e"}
	middle := all[1:4]

	slices.Reverse(middle)

	fmt.Println(all)
	fmt.Println(middle)
}

The output is:

[a d c b e]
[d c b]

Only the range represented by middle is reversed. The elements outside that range remain untouched, but all observes the exchanged values because both slice headers refer to the same array.

This makes slices.Reverse suitable when mutation is intentional. When the original ordering must remain available, make a copy before reversing:

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

values and reversed then have independent backing storage for their elements, subject to the usual shallow-copy semantics of slice elements.

Element values are exchanged, not deeply copied

Reversal operates on elements as Go values. If a slice contains pointers, maps, slices, or structs that contain reference-like fields, the function moves those element values between positions. It does not recursively duplicate the data they reference.

Consider a slice of pointers:

type Record struct {
	ID int
}

a := &Record{ID: 1}
b := &Record{ID: 2}

records := []*Record{a, b}
slices.Reverse(records)

After the call, records[0] is the same pointer value previously stored at records[1], and records[1] is the same pointer previously stored at records[0]. The two Record objects themselves are not copied or altered.

The same distinction applies to structs. A struct element is assigned as a value during the exchange, but fields such as pointers or nested slices retain their normal reference relationships.

Nil and short slices need no special branch

A nil slice has length zero, so there are no elements to exchange:

var values []int
slices.Reverse(values)

The call is valid and values remains nil. Empty non-nil slices behave similarly.

A one-element slice also remains unchanged. These cases follow directly from the operation’s bounds, which means callers generally do not need a separate length check before invoking slices.Reverse.

Calling the function twice restores the original order:

slices.Reverse(values)
slices.Reverse(values)

That property follows from exchanging each pair a second time. It can be useful when code temporarily needs the opposite traversal order and owns the slice mutation, though direct reverse-index iteration may be clearer when changing storage is unnecessary.

Reversal and reverse traversal are different operations

Sometimes code only needs to visit elements from the end toward the beginning. Mutating the slice for that purpose changes shared state that may not be needed.

A reverse index loop leaves storage untouched:

for i := len(values) - 1; i >= 0; i-- {
	process(values[i])
}

This form is appropriate when order should remain stable after traversal. slices.Reverse instead expresses a lasting change to the slice’s element order.

The distinction becomes more significant when aliases exist. A reverse traversal affects no other view of the array, while an in-place reversal is observable through every overlapping view.

Reversing a subslice has precise boundaries

Because the function receives a slice rather than an array pointer plus explicit indices, the slice expression itself defines the mutation boundary:

values := []int{1, 2, 3, 4, 5, 6}
slices.Reverse(values[1:5])

The resulting slice is:

[1 5 4 3 2 6]

This can make localized reordering concise. Capacity beyond the subslice’s length does not expand the affected region; slices.Reverse only accesses indices within the supplied slice length.

That boundary is useful when a larger buffer contains several logical regions. It also reinforces that capacity and mutation range are separate concepts: extra capacity permits future growth through operations such as append, but it does not grant Reverse a wider range.

Named slice types retain their type at the call boundary

The S ~[]E constraint permits slice types whose underlying type is []E:

type IDs []int

ids := IDs{3, 7, 9}
slices.Reverse(ids)

No conversion to []int is required. Since the function returns no value, there is no result type to reconstruct or convert afterward.

This is a small but useful property of the generic signature. Domain-specific slice types can use the standard operation directly while keeping their declared type in surrounding code.

In-place reversal is a storage decision

slices.Reverse is narrow: it exchanges element positions within the supplied slice and leaves the slice’s length, capacity, and backing-array relationship intact. That makes its behavior easy to state, but aliasing determines how far the visible effect reaches.

When the new order should replace the old one, direct reversal avoids an extra destination slice. When both orders must coexist, cloning first separates the storage. When only visitation order changes, reverse traversal avoids mutation altogether.

The right choice depends less on the element type than on ownership of the backing array and whether other code must continue to observe the original ordering.