Go

Compare the performance of string concatenation approaches: +, fmt.Sprintf, strings.Builder, bytes.Buffer, strings.Join.

Question 392MediumGo 1.22 to 1.25

Strings are immutable, so s += x in a loop allocates a new string and copies everything each iteration: O(n²) bytes copied and n allocations.

func concatPlus(parts []string) string {
    var s string
    for _, p := range parts {
        s += p // n allocations, quadratic copying
    }
    return s
}

func concatBuilder(parts []string) string {
    n := 0
    for _, p := range parts {
        n += len(p)
    }
    var sb strings.Builder
    sb.Grow(n) // one allocation
    for _, p := range parts {
        sb.WriteString(p)
    }
    return sb.String() // no copy: returns the underlying bytes as a string
}
  • a + b + c in a single expression is fine: the compiler emits one concatstrings call with a single allocation.
  • fmt.Sprintf: slowest — reflection-based formatting and boxing arguments into any. Fine for logs/errors, not for hot paths; use strconv.AppendInt and friends instead.
  • strings.Builder: best for building strings; String() is zero-copy.
  • bytes.Buffer: similar speed, but String() copies; use it when you also need io.Reader or will read bytes back.
  • strings.Join: when you already have a slice — it pre-computes the length and allocates once.

Gotcha: Grow matters; without it Builder grows geometrically (amortized fine, but several allocations).

More on Performance, Profiling & Testing

All 38 Performance, Profiling & Testing questions