Efficiently Filling Arrays and Slices in Go: A Performance Guide

Vishal Jain
Towards Dev
Published in
4 min readFeb 17, 2025

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

--

--

Published in Towards Dev

A publication for sharing projects, ideas, codes, and new theories.

Written by Vishal Jain

Learning a new thing at a time, problem solver and a knowledge seeker , SDE-III at Bookmyshow, ex-Connectwise, Zycus

Responses (1)

Write a response