slices.Insert places one or more values at a specific slice index and shifts the existing suffix to make room. The operation modifies slice storage when possible, but it can also return a slice backed by a new array when existing capacity cannot hold the expanded result.

Its generic signature is:

func Insert[S ~[]E, E any](s S, i int, v ...E) S

The returned slice must replace the previous slice value because insertion changes the length and can change the backing array.

The insertion index names a boundary

An index passed to Insert identifies the boundary before the element currently at that index. Values at lower indexes remain before the inserted block, while the suffix beginning at i moves toward the end.

package main

import (
    "fmt"
    "slices"
)

func main() {
    names := []string{"api", "worker", "cache"}
    names = slices.Insert(names, 1, "proxy", "queue")

    fmt.Println(names)
}

The result is:

[api proxy queue worker cache]

Index zero inserts at the front. Using len(s) appends the supplied values after the existing elements. An index greater than len(s) is invalid and causes a panic.

This boundary model is useful when the index already comes from another slice operation. An index returned by a search can identify the exact point where a new block belongs without manually splitting and joining the slice.

Insertion can reuse the backing array

A slice with enough spare capacity can accommodate inserted values in its existing backing array. The suffix is moved to higher indexes, then the inserted values occupy the opened range.

values := make([]int, 3, 8)
copy(values, []int{10, 20, 30})

values = slices.Insert(values, 1, 11, 12)

fmt.Println(values) // [10 11 12 20 30]

The logical result has length five. Existing capacity is sufficient, so no larger capacity is required merely to represent that result.

Code should still avoid treating backing-array identity as part of the API contract. The useful guarantee is the returned sequence, not a promise that a particular allocation strategy will be used.

Insufficient capacity can move the result

When the current backing array cannot represent the expanded slice, Insert can allocate new storage. Existing elements and inserted values are then represented by the returned slice.

values := []int{1, 3}
values = slices.Insert(values, 1, 2)

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

Ignoring the return value is therefore incorrect even in code where a particular call appears to have spare capacity. The caller’s old slice header retains its old length, and an allocation can make it refer to different storage from the actual result.

This property matches the broader rule for slice operations that may resize a sequence: use the returned slice as the authoritative value after the call.

Aliases make in-place insertion observable

Two slices can share a backing array. If insertion reuses that array, shifting the suffix can change values visible through another alias.

base := make([]int, 3, 6)
copy(base, []int{10, 20, 30})
alias := base[:3]

base = slices.Insert(base, 1, 15)

fmt.Println(base)  // [10 15 20 30]
fmt.Println(alias) // shared storage has been rearranged

The alias should not be treated as an independent snapshot of the original sequence. Its length is unchanged, but positions within the shared array may now contain values moved or written by the insertion.

If independent outer slice storage is required before mutation, slices.Clone expresses that requirement directly. Capacity manipulation alone is not a reliable substitute for establishing ownership.

Inserted reference values remain references

Insert works for any element type. When an element is a pointer, map, slice, channel, or another reference-bearing value, insertion copies that value into the destination position. It does not recursively duplicate the object reached through it.

type Config struct {
    Enabled bool
}

cfg := &Config{}
items := []*Config{{Enabled: false}}
items = slices.Insert(items, 0, cfg)

cfg.Enabled = true
fmt.Println(items[0].Enabled) // true

The inserted pointer and cfg still identify the same Config. Separate nested state requires an explicit copy appropriate to the element type.

An empty insertion leaves the sequence unchanged

Passing no values does not add elements:

values := []int{4, 8}
values = slices.Insert(values, 1)

fmt.Println(values) // [4 8]

The index must still be valid. This keeps index validation consistent even when the variadic value list is empty.

For an empty result, the function preserves the nil state of the source slice. In ordinary non-empty insertions, the result necessarily has storage for the inserted elements.

Insert has linear movement costs

The standard library documents Insert as O(len(s) + len(v)). Inserting near the front of a long slice requires moving a large suffix, while inserting at the end avoids shifting existing suffix elements but still incorporates the supplied values.

That cost model matters when insertion is repeated inside a loop. A slice is contiguous storage, so inserting many individual elements near the front repeatedly can require repeated movement of existing data. When the final arrangement is already known, constructing the destination sequence in larger contiguous pieces can avoid expressing the work as many separate insertions.

slices.Insert is most precise when the operation itself is a positional edit: preserve the prefix, place a value block at a known boundary, and retain the suffix after it. Its returned-slice contract keeps allocation flexible while making the resulting sequence explicit.