Repeating a short slice pattern is simple enough to write by hand, but the bookkeeping gets noisy: calculate the final size, allocate storage, then append or copy the pattern the right number of times. Since Go 1.23, slices.Repeat handles that operation directly.
The function is useful when the thing you want to repeat is already a slice: a test fixture pattern, a protocol marker, a sequence of defaults, or any other small block of values. It returns a new slice rather than extending or rearranging the input.
What slices.Repeat does
The signature is:
func Repeat[S ~[]E, E any](x S, count int) SGiven a slice and a count, slices.Repeat concatenates that slice with itself count times.
package main
import (
"fmt"
"slices"
)
func main() {
pattern := []string{"read", "write"}
steps := slices.Repeat(pattern, 3)
fmt.Println(steps)
fmt.Println("len:", len(steps), "cap:", cap(steps))
}The output is:
[read write read write read write]
len: 6 cap: 6The result has both length and capacity equal to len(pattern) * count. That makes the size predictable without a separate capacity calculation at the call site.
This differs from repeating a single value. If the input is []int{1, 2, 3}, a count of 2 produces []int{1, 2, 3, 1, 2, 3}. It doesn’t produce two copies of each individual element next to each other.
The returned slice has its own top-level storage
slices.Repeat returns a new slice. Changing an element in the original slice afterward doesn’t rewrite the corresponding positions in the result.
package main
import (
"fmt"
"slices"
)
func main() {
pattern := []string{"primary", "backup"}
roles := slices.Repeat(pattern, 2)
pattern[0] = "changed"
fmt.Println(pattern) // [changed backup]
fmt.Println(roles) // [primary backup primary backup]
}That separation is useful when the repeated result needs to survive changes to the source slice.
There is a caveat for reference-like element values. The slice elements themselves are repeated; slices.Repeat doesn’t recursively clone objects they refer to. If the elements are pointers, each repetition contains the same pointer value. If they are slices or maps, their underlying referenced data is still shared.
For example:
row := []int{1, 2}
rows := slices.Repeat([][]int{row}, 2)
rows[0][0] = 99
fmt.Println(rows) // [[99 2] [99 2]]Both outer elements refer to the same row backing array. If each repetition needs independent nested data, create those nested values separately instead of relying on Repeat as a deep-copy operation.
Count zero returns an empty, non-nil slice
A zero count is valid. The result contains no elements, even when the input contains values.
values := slices.Repeat([]int{10, 20}, 0)
fmt.Println(len(values)) // 0
fmt.Println(values == nil) // falseThe result from slices.Repeat is never nil. That detail can matter in code that deliberately distinguishes nil and empty slices, such as custom serialization or API boundary logic.
The same rule applies when the input itself is nil:
var input []int
result := slices.Repeat(input, 3)
fmt.Println(len(result)) // 0
fmt.Println(result == nil) // falseIf nilness carries meaning in your program, don’t assume Repeat preserves it. Normalize the result explicitly or choose a different construction when that distinction is part of the contract.
Negative counts and oversized results panic
A negative repetition count isn’t interpreted as zero. slices.Repeat panics when count is negative.
slices.Repeat([]int{1, 2}, -1) // panicIt also panics if len(x) * count would overflow. In ordinary application code, the more practical concern is often memory long before integer overflow: a large valid count can request a very large allocation.
That means a count coming from a request, configuration file, or other untrusted input should usually be bounded before calling Repeat.
func repeatPattern(pattern []byte, count int) ([]byte, error) {
const maxCount = 1024
if count < 0 || count > maxCount {
return nil, fmt.Errorf("repeat count %d is out of range", count)
}
return slices.Repeat(pattern, count), nil
}The right limit depends on the size of the pattern and what the result is used for. A fixed count limit is easy to understand, but a byte or element budget may fit better when input pattern sizes vary substantially.
Prefer Repeat over a manual append loop when the result is the goal
A hand-written implementation commonly looks like this:
result := make([]string, 0, len(pattern)*count)
for range count {
result = append(result, pattern...)
}That code is reasonable when the loop needs extra work on every repetition. If all it does is build repeated copies, though, slices.Repeat(pattern, count) says exactly what the result should be and avoids duplicating size calculations around the codebase.
There are cases where the loop remains the better choice. Suppose every block needs a generated sequence number, timestamp, or independent nested object. At that point you’re constructing distinct values, not merely repeating a slice pattern. Making that distinction explicit keeps Repeat from hiding work that really belongs in a loop.
Don’t confuse slices.Repeat with an iterator
Despite arriving alongside several iterator-oriented additions to slices in Go 1.23, slices.Repeat returns a fully materialized slice. It allocates space for the complete result before you process it.
That is a good fit when downstream code needs a slice, its length, random indexing, or repeated traversal. It is less attractive when you only need to stream a pattern many times and can process each value immediately. A lazy iterator can avoid holding the whole repeated sequence in memory.
The distinction becomes noticeable when either the pattern or count is large. Repeat is intentionally a collection-building operation, not an infinite or on-demand repetition primitive.
Use slices.Repeat when you need the repeated collection
slices.Repeat is a concise choice when you already have a slice pattern and need a concrete slice containing several copies of it. The result has an exact length and capacity, doesn’t share its top-level storage with the input, and handles zero repetitions without special branching.
Before using it with nested reference-like values, decide whether shared inner data is acceptable. And when the count comes from outside the program, bound the requested result before allocating it. With those constraints clear, slices.Repeat replaces a common allocation-and-append loop with a direct description of the data you want.