Go

Why are strings immutable in Go, and what does converting between string and []byte cost?

Question 67HardGo 1.22 to 1.25

A string is a two-word header, {ptr *byte, len int}, pointing at read-only bytes. Immutability means strings can be shared freely: substrings and assignments copy no data, strings are safe to use across goroutines without locks, and they work as map keys whose hash can never go out of date. String literals sit in read-only memory.

The cost is that []byte(s) and string(b) normally allocate and copy. The copy is required: otherwise changing the byte slice would change the "immutable" string. The compiler removes the copy in several known cases:

b := []byte("hello")

// No allocation in these cases (compiler optimizations):
m := map[string]int{}
_ = m[string(b)]                 // map lookup with converted key
if string(b) == "hello" {}       // comparison
_ = "x" + string(b) + "y"        // temporary in concatenation (result still allocates)
for i, c := range []byte("abc") { _, _ = i, c } // no copy for range
switch string(b) { case "a": }   // switch on converted bytes

// Small non-escaping conversions (<= 32 bytes) may use a stack buffer.

// Allocates:
s := string(b)                   // copy: b may change later
b2 := []byte(s)                  // copy: s must stay immutable
_, _ = s, b2

Performance tips:

  • Use APIs that work on []byte directly: the bytes package mirrors strings.
  • io.WriteString and w.Write accept either form without converting.
  • Check allocations with go test -bench . -benchmem and -gcflags=-m.

More on Arrays, Slices, Maps & Strings

All 37 Arrays, Slices, Maps & Strings questions