bytes.Buffer for string concatenation in Go

The differences between methods of string concatenation in Go.

July 13, 2015

Just thought I would write a quick reminder on how to properly do string concatenation in Go.

Every now and again I see string concatenation done like this: myString := myOtherString + anotherString. This is not the most effective way.

Let me show you an example. Recently I quickly wrote a library to slugify titles for one of my personal projects. When hacking it together I used string concatenation like demonstrated earlier.

slugify-slow.go

// Slugify creates a slug for a string
func (s Slugifier) Slugify(value string) string {

    value = strings.ToLower(value)
    res := ""
    lastCharacterWasInvalid := false

    for i := 0; i < len(value); i++ {
        c := value[i]
        if s.isValidCharacter(c) {
            res = res + string(c)
            lastCharacterWasInvalid = false
        } else if lastCharacterWasInvalid == false {
            res = res + string(s.replaceCharacter)
            lastCharacterWasInvalid = true
        }
    }

    return strings.Trim(res, string(s.replaceCharacter))
}

All swell, now see the benchmark for this piece of code:

slugify-slow.go benchmark

$ go test -bench=.
PASS
BenchmarkSlugify            500000        2794 ns/op
BenchmarkSlugifyLongString  2000      552774 ns/op
ok      github.com/Machiel/slugify  2.609s

When I saw this, I was not satisfied, and I knew I could improve it by using bytes.Buffer instead.

This is how you would write myString := myOtherString + anotherString using bytes.Buffer.

var buffer bytes.Buffer
buffer.WriteString(myOtherString)
buffer.WriteString(anotherString)
myString := buffer.String()

Easy, huh? Now let’s take a look at the changed code and and the drastically improved results:

slugify-less-slow.go

// Slugify creates a slug for a string
func (s Slugifier) Slugify(value string) string {

    value = strings.ToLower(value)
    var buffer bytes.Buffer
    lastCharacterWasInvalid := false

    for i := 0; i < len(value); i++ {
        c := value[i]
        if s.isValidCharacter(c) {
            buffer.WriteByte(c)
            lastCharacterWasInvalid = false
        } else if lastCharacterWasInvalid == false {
            buffer.WriteByte(s.replaceCharacter)
            lastCharacterWasInvalid = true
        }
    }

    return strings.Trim(buffer.String(), string(s.replaceCharacter))
}

slugify-less-slow.go benchmark

$ go test -bench=.
PASS
BenchmarkSlugify            1000000       1269 ns/op
BenchmarkSlugifyLongString  30000      42735 ns/op
ok      github.com/Machiel/slugify  2.979s

As you can see, there is around a 100% performance gain for the BenchmarkSlugify and around a 50% performance gain for the BenchmarkSlugifyLongString.

So that’s it, use bytes.Buffer :).

Quick tip, for simple concatenations, you could use a function like this:

func concat(values ...string) string {
    var buffer bytes.Buffer
    for _, s := range values {
        buffer.WriteString(s)
    }
    return buffer.String()
}

Or, alternatively, you could write this: strings.Join([]string{"Hi", ",", " ", "World!"}, ""). Both are faster than myString := "Hi" + "," + " " + "World!"! I simply prefer the concat function, because I find it more readable.