slices.Compact removes repeated values only when they occur next to each other. That detail makes it different from set-based deduplication: the function collapses equal runs, preserves their order, and modifies the supplied slice storage.

The operation fits data that is already grouped by value, including sorted slices and streams that naturally produce repeated adjacent states. It does not search the full slice for every matching value.

Compact collapses consecutive runs

The generic signature accepts slices whose element type is comparable:

func Compact[S ~[]E, E comparable](s S) S

A run of equal values becomes one value:

package main

import (
    "fmt"
    "slices"
)

func main() {
    values := []int{2, 2, 2, 5, 5, 8, 2, 2}
    values = slices.Compact(values)

    fmt.Println(values) // [2 5 8 2]
}

The final 2 remains because the 8 separates it from the earlier run. Compact is therefore a run-compaction operation rather than a global distinct-value operation.

This distinction becomes useful when order carries meaning. A sequence such as open, open, closed, closed, open can be reduced to open, closed, open without discarding the later transition back to open.

Sorting changes the meaning of compaction

When the desired result is one copy of each comparable value and original order is not required, sorting before compaction can turn equal values into adjacent runs:

values := []int{8, 2, 5, 2, 8, 5, 2}

slices.Sort(values)
values = slices.Compact(values)

fmt.Println(values) // [2 5 8]

That combination has different semantics from calling Compact on the original sequence. Sorting first discards the original ordering relationship. It is suitable only when that reordering is acceptable to the surrounding code.

For data that must retain first-occurrence order while removing duplicates across the full slice, a map-backed pass expresses a different operation and should not be replaced mechanically with Compact.

The returned slice must replace the old slice value

Compaction can reduce the slice length. A function cannot change the caller’s copy of a slice header directly, so Compact returns the updated slice value.

names := []string{"api", "api", "worker"}
names = slices.Compact(names)

Ignoring the return value leaves the variable with its original length even though the backing array has been modified. Code should treat the pre-call slice value as stale after an in-place operation that returns a resized slice.

This follows the same general shape as several other mutating functions in the slices package: the backing storage can be reused while the returned header describes the valid result.

Compaction reuses slice storage

Compact modifies the supplied slice rather than promising a separate backing array. Values retained from later positions may be moved toward the front as duplicate runs are removed.

That storage reuse matters when another slice aliases the same backing array:

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

values = slices.Compact(values)

fmt.Println(values) // [1 3 5]
fmt.Println(alias)  // backing storage has been modified

The exact contents observed through alias after the call should not be used as a second independent version of the original data. If the original sequence must remain intact, clone it before compaction:

copyOfValues := slices.Clone(values)
copyOfValues = slices.Compact(copyOfValues)

The clone establishes separate outer slice storage before the in-place transformation.

Obsolete tail elements are cleared

After compaction, the returned slice can be shorter than the original. Current Go implementations of the standard slices API define Compact to zero the elements between the new length and the old length.

For pointer-bearing element types, clearing those obsolete slots prevents removed references in the old tail from keeping referenced objects reachable solely through stale slice storage.

Consider a slice of pointers with repeated adjacent pointer values:

type Record struct {
    ID int
}

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

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

fmt.Println(len(records)) // 2
fmt.Println(records[0].ID, records[1].ID) // 1 2

The result contains one pointer from each equal run. Slots made obsolete by the shorter result are set to the zero value for the element type.

Nil state is preserved

A nil input remains nil:

var values []int
values = slices.Compact(values)

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

A non-nil empty slice remains non-nil. This keeps the input’s nil distinction intact while still returning the slice value that represents the compacted result.

Comparable elements define equality directly

Compact uses Go equality for its element type, so E must satisfy comparable. Strings, integers, pointers, and structs composed entirely of comparable fields fit that constraint. Slices and maps do not.

When equality needs application-specific logic, slices.CompactFunc is the related operation. It accepts an equality function and can handle element types or equivalence rules that plain == cannot express.

For direct comparable values, slices.Compact keeps the operation narrower: adjacent equality, in-place storage reuse, a potentially shorter returned slice, and cleared obsolete tail slots. Those properties make it a precise fit for collapsing runs without introducing a separate set or allocation solely for deduplication.