Why Can Simple String Concatenation in Go Become a Problem?
\n\nEveryone who starts learning Go sooner or later faces a task: you need to glue several strings into one. The most obvious way is to use the += operator or simple addition +. And it works… until you try to glue together a thousand strings. Suddenly your code starts to slow down, and memory consumption goes through the roof.
Why does this happen? In Go, strings are immutable. When you write s += "new", Go does not append text to the end of the old string. It creates a new string, copying the contents of both parts into it. Each operation allocates new memory. If there are thousands of such operations, the garbage collector starts working in emergency mode, and performance drops.
In this article, we will cover all the ways to concatenate strings in Go: from naive to production-ready. You will learn which method to use and when, and will be able to write code that you won't be ashamed to show at a code review.
\n\nString Concatenation Methods in Go: From Simple to Efficient
\n\nLet's look at five main approaches. For each, I will provide a code example and explain in which situations it is appropriate.
\n\n1. The + and += Operators (Naive Method)
\n\nThe simplest and most intuitive method. Ideal for gluing together 2–5 strings.
\n\npackage main\n\nimport "fmt"\n\nfunc main() {\n hello := "Hello"\n world := "World"\n result := hello + ", " + world + "!"\n fmt.Println(result) // Hello, World!\n}\n\n\nWhen to use: only for a small number of strings. If you are writing a loop with 1000 iterations using += — stop. This has quadratic memory complexity (O(n²)).
2. fmt.Sprintf
\n\nConvenient when you need to format output. Internally, it also creates new strings, but does so more efficiently than manual gluing.
\n\nname := "Alice"\nage := 30\nresult := fmt.Sprintf("My name is %s and I am %d years old.", name, age)\nfmt.Println(result)\n\n\nWhen to use: when you need not just to glue, but to format a string with different data types. For simple concatenation, it's not the best choice due to the overhead of parsing the template.
\n\n3. strings.Join — The King for String Slices
\n\nIf you already have an array or slice of strings, strings.Join is the fastest and most elegant way.
package main\n\nimport (\n "fmt"\n "strings"\n)\n\nfunc main() {\n words := []string{"Go", "is", "awesome", "for", "backend"}\n result := strings.Join(words, " ")\n fmt.Println(result) // Go is awesome for backend\n}\n\n\nInternally, strings.Join first calculates the total length of the result, allocates memory once, and fills it. No unnecessary copying.
When to use: whenever you have a ready-made slice of strings. This is the ideal option for building SQL queries, CSV strings, and URLs.
\n\n4. bytes.Buffer — A Classic
\n\nThe bytes package provides a buffer that grows dynamically. It implements the io.Writer interface, so you can write to it using WriteString.
package main\n\nimport (\n "bytes"\n "fmt"\n)\n\nfunc main() {\n var buffer bytes.Buffer\n\n for i := 0; i < 5; i++ {\n buffer.WriteString("Line ")\n buffer.WriteString(fmt.Sprintf("%d\\n", i))\n }\n\n fmt.Println(buffer.String())\n}\n\n\nThe buffer allocates memory in chunks (usually 64 bytes), which significantly reduces the number of allocations compared to +=.
When to use: in loops where the number of strings is not known in advance. For example, when generating HTML or logs.
\n\n5. strings.Builder — The Modern Standard (Go 1.10+)
\n\nStarting with Go 1.10, a special