Why you need to avoid using append in Go

Daryl Ng
2 min readFeb 6, 2021

append is the go to function when you are adding an element to a slice. But is it the best way to do so?

Short answer, no it isn’t.

Here’s why…

First, let’s create two functions that adds or assigns "x" to a string slice.

  1. WithAppend calls append to add "x" to a string slice. This is probably the most straightforward method.
func WithAppend() []string {
var l []string
for i := 0; i < 100; i++ {
l = append(l, "x")
}

return l
}

2. WithAssignAlloc also creates a string slice by specifying the size with make , but assigns "x" instead of using append .

func WithAssignAlloc() []string {
l := make([]string, 100)
for i := 0; i < 100; i++ {
l[i] = "x"
}

return l
}

These two functions return the same output but are implemented very differently. Now, let’s benchmark these functions.

func BenchmarkWithAppend(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
WithAppend()
}
}
func BenchmarkWithAssignAlloc(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
WithAssignAlloc()
}
}

And here are the results…

BenchmarkWithAppend
BenchmarkWithAppend-8 863949 1322 ns/op 4080 B/op 8 allocs/op
BenchmarkWithAssignAlloc
BenchmarkWithAssignAlloc-8 2343424 523 ns/op 1792 B/op 1 allocs/op

WithAppend has the worst while WithAssignAlloc have the best performance, and this should convince you to avoid append .

Hold your horses…

Let’s add one more function which uses append and create a string slice by specifying the size and capacity with make .

func WithAppendAlloc() []string {
l := make([]string, 0, 100)
for i := 0; i < 100; i++ {
l = append(l, "x")
}
return l
}

And run the benchmark again.

BenchmarkWithAppend
BenchmarkWithAppend-8 863949 1322 ns/op 4080 B/op 8 allocs/op
BenchmarkWithAppendAlloc
BenchmarkWithAppendAlloc-8 2543119 514 ns/op 1792 B/op 1 allocs/op
BenchmarkWithAssignAlloc
BenchmarkWithAssignAlloc-8 2343424 523 ns/op 1792 B/op 1 allocs/op

Voila! Now we are getting similar performance for WithAppendAlloc and WithAssignAlloc . But what just happened?!

By adding more elements in WithAppend, we needed to grow the slice by creating a new and larger slice, resulting in multiple allocations.

But before you go ahead and change your code, you should benchmark to find bottlenecks in your code. The above example is overly simplified and you may not always know the size to allocate the slice.

Premature performance tuning may be overkill. You should always determine if such changes are necessary to provide any performance improvement.

I hope you find this useful. Do give it a clap or share it if you think so.

Do follow me if you have not already done so!

Edit: Thanks Mike Schinkel and Jan Pfeifer for pointing out the mistake in AppendAlloc.

Edit 2: As per Jon Jenkins suggestion, do check out https://blog.golang.org/slices-intro to get a better understanding of make and append.

--

--