Efficiently Filling Arrays and Slices in Go: A Performance Guide
When working on a Go project, you might need to fill a slice or array with a repeating pattern. Recently, I explored different approaches to achieve this efficiently and discovered a powerful way to boost performance. In this article, I’ll walk through various techniques to fill a slice in Go, highlight their performance differences, and explain why one method stands out as significantly faster.

Why Slice Filling Matters
For my toy project, I needed to fill a background buffer with a specific RGB color pattern. Optimizing this operation significantly improved my achievable frame rate. The insights I gained could be useful for anyone working with large datasets, graphics programming, or high-performance applications.
All tests were performed on a buffer of 73,437 bytes, allocated as:
var bigSlice = make([]byte, 73437)
Let’s compare three common ways to fill this slice.
Index-based Loop
The simplest way is to iterate through each index and set the value.
func FillSliceIndex(slice []byte, value byte) {
for j := 0; j < len(slice); j++ {
slice[j] = value
}
}
func Benchmark_FillsliceIndex(b *testing.B) {
slice := make([]byte, 73437)
for i := 0; i < b.N; i++ {
FillSliceIndex(slice, 65)
}
}
Benchmark Results:
| Name | Executions | Time/Op | Bytes/Op | Allocs/Op |
| --------------------------- | ---------- | ------------ | -------- | ----------- |
| Benchmark_FillsliceIndex-4 | 24,945 | 45,540 ns/op | 0 B/op | 0 allocs/op |
Range-based Loop
Using a range loop provides a slight performance improvement:
func FillSliceRange(slice []byte, value byte) {
for j := range slice {
slice[j] = value
}
}
func Benchmark_FillsliceRange(b *testing.B) {
slice := make([]byte, 73437)
for i := 0; i < b.N; i++ {
FillSliceRange(slice, 66)
}
}
Benchmark Results:
| Name | Executions | Time/Op | Bytes/Op | Allocs/Op |
| --------------------------- | ---------- | ------------ | -------- | ----------- |
| Benchmark_FillsliceRange-4 | 35,086 | 34,316 ns/op | 0 B/op | 0 allocs/op |
The Copy Trick (Efficient Method)
The most efficient approach leverages Go’s built-in copy function to fill the slice incrementally:
func FillSliceCopyTrick(slice []byte, value byte) {
slice[0] = value
for j := 1; j < len(slice); j *= 2 {
copy(slice[j:], slice[:j])
}
}
func Benchmark_FillsliceCopyTrick(b *testing.B) {
slice := make([]byte, 73437)
for i := 0; i < b.N; i++ {
FillSliceCopyTrick(slice, 67)
}
}
Benchmark Results:
| Name | Executions | Time/Op | Bytes/Op | Allocs/Op |
| ------------------------------- | ---------- | ----------- | -------- | ----------- |
| Benchmark_FillsliceCopyTrick-4 | 749,976 | 1,579 ns/op | 0 B/op | 0 allocs/op |
Filling with a Multi-Element Pattern
If you need to fill the slice with a repeating multi-byte pattern, you can adapt the copy trick easily:
func FillSlicePatternCopyTrick(slice []byte, pattern []byte) {
copy(slice, pattern)
for j := len(pattern); j < len(slice); j *= 2 {
copy(slice[j:], slice[:j])
}
}
func Benchmark_FillslicePatternCopyTrick(b *testing.B) {
slice := make([]byte, 73437)
pattern := []byte{0xde, 0xad, 0xbe, 0xef}
for i := 0; i < b.N; i++ {
FillSlicePatternCopyTrick(slice, pattern)
}
}
Benchmark Results:
| Name | Executions | Time/Op | Bytes/Op | Allocs/Op |
| -------------------------------------- | ---------- | ----------- | -------- | ----------- |
| Benchmark_FillslicePatternCopyTrick-4 | 798,944 | 1,563 ns/op | 0 B/op | 0 allocs/op |
Summary of Benchmark Results
| Name | Executions | Time/Op | Bytes/Op | Allocs/Op |
| -------------------------------------- | ---------- | ------------ | -------- | ----------- |
| Benchmark_FillsliceIndex-4 | 24,945 | 45,540 ns/op | 0 B/op | 0 allocs/op |
| Benchmark_FillsliceRange-4 | 35,086 | 34,316 ns/op | 0 B/op | 0 allocs/op |
| Benchmark_FillsliceCopyTrick-4 | 749,976 | 1,579 ns/op | 0 B/op | 0 allocs/op |
| Benchmark\_FillslicePatternCopyTrick-4 | 798,944 | 1,563 ns/op | 0 B/op | 0 allocs/op |
How and Why the Copy Trick Works
The copy function avoids the overhead of indexing and bounds checking each element. Here’s how it works:
- The first value (or pattern) is loaded into the slice.
- Each call to `copy` duplicates twice the amount of data as the previous iteration.
- This exponential growth reduces the number of copy operations required, amortizing the cost.
- The final copy naturally stops when the slice is filled — no bounds checks are needed.
Final Thoughts
If you ever need to efficiently fill a slice or array in Go, especially for large datasets, the copy trick is a powerful and elegant solution. It’s faster, avoids unnecessary allocations, and leverages built-in optimizations for block memory operations.
I hope this guide helps you optimize your Go code and improve your application’s performance. Let me know if you discover any other cool slice-filling techniques!
Happy coding! 🚀
Reference
For the complete source code and more details, check out the GitHub repository