Day 034 — strings.Builder & Efficient Concatenation¶
Month 2 · Week 1 · ⬅ Day 033 · Day 035 ➡ · Journal index
🎯 Learning Objective¶
Understand why += in a loop is quadratic, and use strings.Builder (and friends) to build strings with minimal allocations.
📚 Topics¶
- String immutability → why
+=reallocates strings.Builder:WriteString/WriteByte/WriteRune/Grow/Stringstrings.Join,bytes.Buffer, and when to pick which- The "don't copy a Builder" rule
📖 Reading / Sources¶
📝 Notes¶
- Strings are immutable.
s += xcan't growsin place — it allocates a new string and copies both operands. In a loop ofnappends that's O(n²) total copying → [[string-immutability]]. strings.Builderwrites into a single internal[]bytethat grows geometrically (amortized O(n)), and itsString()returns the buffer without copying (it uses an unsafe conversion internally) → [[strings-builder]].- API:
WriteString,WriteByte,WriteRune, andWrite([]byte). Builder satisfiesio.Writer, sofmt.Fprintf(&b, ...)works too. b.Grow(n)pre-reserves capacity when you can estimate the final size, eliminating intermediate regrowth.- Do not copy a
strings.Builderafter first use — it holds a pointer to detect misuse and willpanic. Pass it by pointer (*strings.Builder), never by value → [[builder-no-copy]]. - For a known set of pieces,
strings.Join(parts, sep)is simplest and already optimal — reach forBuilderwhen you're appending in a loop with logic between writes. bytes.Bufferalso builds efficiently and is read+write; preferstrings.Builderwhen you only need to produce a string (it skips the final copy), andbytes.Bufferwhen you also need to read the bytes back or hand off anio.Reader.- Rule of thumb table:
- Few fixed pieces →
a + b + corstrings.Join. - Loop building a string →
strings.Builder. - Need both read and write of bytes →
bytes.Buffer.
💻 Code Examples¶
var b strings.Builder
b.Grow(16) // optional: preallocate if size is roughly known
for n := 0; n < 5; n++ {
fmt.Fprintf(&b, "%d,", n) // &b — never copy a Builder by value
}
out := strings.TrimRight(b.String(), ",") // "0,1,2,3,4"
_ = out
Full code:
examples/month-02/strings-strconv/main.go· Run:go run ./examples/month-02/strings-strconv
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
Slugify text with strings.Builder (no += in loop) |
✅ | exercises/month-02/week-1/slugify |
🐛 Mistakes Made¶
- Built a CSV line with
s += field + ","in a hot loop; switching tostrings.Buildercut allocations dramatically. - Passed a
strings.Builderby value to a helper →panic: strings: illegal use of non-zero Builder copied by value. Switched to*strings.Builder.
❓ Open Questions¶
- Roughly how big does the loop need to be before
Builderbeats naive+=in practice (with-benchmem)?
🧠 Active Recall (answer without looking)¶
-
Q: Why is
s += xinside a loop a performance trap?
A
Strings are immutable, so each `+=` allocates a brand-new string and copies all existing bytes — O(n²) work over `n` iterations. `strings.Builder` appends into one growing buffer for amortized O(n). -
Q: What happens if you pass a used
strings.Builderby value?
A
It panics: `illegal use of non-zero Builder copied by value`. Builder tracks a self-pointer to forbid copies; always pass `*strings.Builder`.
🪶 Feynman Reflection¶
Because a Go string can never be edited, "adding to" one really means "make a whole new bigger one and throw the old away." Do that in a loop and you re-copy everything every time — death by a thousand copies. strings.Builder keeps one resizable scratchpad and only hands you a finished string at the end, so you copy the data essentially once.
🕳️ Knowledge Gaps¶
- Writing a Go benchmark (
testing.B,b.ReportAllocs) to prove the difference — coming up in the testing week.
✅ Summary¶
I know why naive concatenation is quadratic and can build strings efficiently with strings.Builder, strings.Join, or bytes.Buffer, choosing the right tool per situation.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 035: Week 1 review and closed-book recall.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦⬜⬜⬜ | 🟦🟦🟦🟦⬜ |
Suggested commit: feat(exercises): slugify with strings.Builder (day 034)