In Go, there are several methods for string concatenation. The simplest one (but not the best one) is to add two strings using the plus (+
) operator. You can also use the fmt.Sprintf()
function if you have a couple of strings that want to combine into one. A more efficient way to concatenate strings is to use the strings.Builder
structure which minimizes memory copying and is recommended for building longer results. Alternatively, you can also use the strings.Join()
function if you know all the strings to join and their separator before calling it, or bytes.Buffer
and byte slices to operate directly on string bytes.
There is no single best method to concatenate strings, so it is a good idea to know most of them, their pros and cons, and use them depending on your use case.
All benchmarks in the examples below were run in Go 1.17.
Use the plus (+
) operator to simply concatenate strings
Using the plus (+
) operator is the simplest way of strings concatenation. However, you should be careful when calling this method because strings in Go are immutable, and every time you add a string to an existing variable, a new string is allocated in memory. As a result, this method is inefficient if you need to concatenate multiple strings, for example, in a loop. In that case, you should use the strings.Builder
method to build the final string.
This and all the following examples give the output:
Hello https://gosamples.dev
😉.
package main
import "fmt"
func main() {
hello := "Hello"
gosamples := "https://gosamples.dev"
result := hello
result += " "
result += gosamples
fmt.Println(result)
}
Pros and cons of concatenating strings using the plus (+
) operator:
- The easiest way of concatenating strings
- No need to use external dependencies
- Inefficient when used to concatenate a long list of strings
Less readable than
fmt.Sprintf()
method when building a formatted string
Use the fmt.Sprintf()
function to format multiple strings into one
The fmt.Sprintf()
function takes a template and arguments and returns this template with the appropriate fields replaced by the arguments. This is the most idiomatic and readable method for creating short strings with variable values but is not suitable for concatenation when you do not know the number of strings in advance.
package main
import "fmt"
func main() {
hello := "Hello"
gosamples := "https://gosamples.dev"
result := fmt.Sprintf("%s %s", hello, gosamples)
fmt.Println(result)
}
Pros and cons of concatenating strings using the fmt.Sprintf()
function:
- A clear and idiomatic way of creating strings with variable values
- Allows to easily create strings from arguments of different types e.g. string, int, bool, etc., without explicit conversion
- Not suitable when you do not know in advance the number of elements to concatenate
Inconvenient for a longer list of arguments
Use strings.Builder
to efficiently concatenate strings
The strings.Builder
was created to build long strings in a simple and efficient way. This method minimizes the number of copies and memory allocations and works particularly well if you have a large list of strings to concatenate or if the process of building the resulting string is multi-step. If you need to perform string concatenation efficiently, this method is the most recommended and natural choice in Go.
package main
import (
"fmt"
"log"
"strings"
)
func main() {
data := []string{"Hello", " ", "https://gosamples.dev"}
var builder strings.Builder
for _, s := range data {
_, err := builder.WriteString(s)
if err != nil {
log.Fatal(err)
}
}
fmt.Println(builder.String())
}
If you know the size of the output in advance, it is a good practice to use the Grow()
method to preallocate the needed memory. This increases the speed of concatenation by avoiding unnecessary copying of partial results:
builder.Grow(27)
Pros and cons of concatenating strings using the strings.Builder
:
- Efficient for concatenating a long list of strings or for building a string in multiple steps
- More complicated to use than the previous methods
Use the strings.Join()
function to create a single string from a slice
The strings.Join()
constructs a single string from joining a fixed slice of strings with a defined separator between them. It uses the strings.Builder
internally. Since the number of elements to be concatenated is known, it allocates the necessary amount of memory, which makes this method very efficient.
package main
import (
"fmt"
"strings"
)
func main() {
hello := "Hello"
gosamples := "https://gosamples.dev"
result := strings.Join([]string{hello, gosamples}, " ")
fmt.Println(result)
}
Pros and cons of concatenating strings using the strings.Join()
function:
- Super-efficient for concatenating a fixed list of strings with the same separator
- Simple and easy to use
- Not suitable when you do not know in advance the number of elements to concatenate or if you want to use different separators
Use bytes.Buffer
to efficiently build a byte buffer
The strings.Builder
was introduced in Go 1.10. Before that, the bytes.Buffer
was used to concatenate strings efficiently. It has similar methods but is slightly slower, so in the new code, you should use the strings.Builder
instead.
package main
import (
"bytes"
"fmt"
"log"
)
func main() {
data := []string{"Hello", " ", "https://gosamples.dev"}
var buffer bytes.Buffer
for _, s := range data {
_, err := buffer.WriteString(s)
if err != nil {
log.Fatal(err)
}
}
fmt.Println(buffer.String())
}
As with the strings.Builder
, you can use the Grow()
method to preallocate the needed memory:
buffer.Grow(27)
Pros and cons of concatenating strings using the bytes.Buffer
:
- Efficient for concatenating a long list of strings or for building a string in multiple steps
Since Go 1.10, there is the
strings.Builder
which has similar methods and is more efficient
Use byte slice to extend a string
Strings in Go are read-only slices of bytes, so there is no problem extending a byte slice of a string by appending bytes of another string. As a result, after converting to a string, we get the concatenated output. However, this method is low-level and less idiomatic than the others. In practice, it is much better to use the strings.Builder
instead.
package main
import (
"fmt"
)
func main() {
data := []string{"Hello", " ", "https://gosamples.dev"}
var byteSlice []byte
for _, s := range data {
byteSlice = append(byteSlice, []byte(s)...)
}
fmt.Println(string(byteSlice))
}
Pros and cons of concatenating strings using appending to a byte slice:
- Easy to use
- Do not require any dependencies
Works on byte slices instead of strings - requires final conversion to string
Not as efficient as the
bytes.Buffer
Benchmarks
To check which method of concatenating strings is the fastest, we prepared a simple benchmark that compares all the above methods. Each of them concatenates 399 elements into a single result. We simulated two variants of concatenation: when we know the size of the result string in advance (benchmarks named Benchmark<XXX>KnownSize
), and when we do not know the exact size of the result string (benchmarks named Benchmark<XXX>UnknownSize
). We did this because some methods are only suitable when we know the number of elements to concatenate (strings.Join()
, fmt.Sprintf()
), some work without considering the number of elements (the plus (+
) operator), and some work in both variants (strings.Builder
, bytes.Buffer
, byte slice):
strings.Join()
requires a slice of strings as an argument, so the number of elements to concatenate must be known before calling this function.fmt.Sprintf()
works for a finite and known number of arguments equal to the number of arguments in the template.- the plus (
+
) operator concatenates only one argument at a time (result += argument
), and you cannot specify how many elements you have or preallocate a fixed amount of memory for the result. strings.Builder
andbytes.Buffer
can build strings when we do not know the number of elements that will finally be in the result. When necessary, they allocate more memory for the output. Therefore, these methods work well for creating strings dynamically, for example, in a loop. On the other hand, when we know the number of elements to concatenate and the size of the result, we can immediately allocate the needed amount of memory using theGrow()
method (available instrings.Builder
as well as inbytes.Buffer
), avoiding unnecessary copying of partial results.- byte slices, like
strings.Builder
andbytes.Buffer
, can be initialized as empty usingvar byteSlice []byte
or with a specified capacity usingbyteSlice := make([]byte, 0, size)
. In this way, this method can be used both with a known number of elements and without specifying the size of the result.
The fastest method does not mean the best. Always consider the readability of the code and try to fit the method to the use case.
|
|
Running the benchmark with the command:
go test -bench=.
we got the results:
BenchmarkPlusOperatorUnknownSize-8 166 12008232 ns/op
BenchmarkSprintfKnownSize-8 184053 6421 ns/op
BenchmarkStringBuilderUnknownSize-8 269620 4365 ns/op
BenchmarkStringBuilderKnownSize-8 422790 2735 ns/op
BenchmarkJoinKnownSize-8 475293 2370 ns/op
BenchmarkBytesBufferUnknownSize-8 219260 5441 ns/op
BenchmarkBytesBufferKnownSize-8 321973 3639 ns/op
BenchmarkByteSliceUnknownSize-8 175533 6803 ns/op
BenchmarkByteSliceKnownSize-8 230568 5046 ns/op
The benchmark is also available as a Github Gist.
- As you can see, the plus (
+
) operator is much slower than the other methods, and you should not use them to concatenate a longer list of elements. - In terms of performance, it is best to use the
strings.Builder
orstrings.Join()
to join a long list of strings. In our case,strings.Join()
turned out to be even faster than thestrings.Builder
. - You get a big speedup when you preallocate the amount of memory necessary for the output.
- The
bytes.Buffer
and byte slice methods are slower and not as readable as thestrings.Builder
because they operate on bytes rather than strings. So, they should not be your first choice for string concatenation.