Why is building a long string by concatenating with += inside a loop inefficient, and what does StringBuilder do differently?
answer
- String immutable -> += copies everything
- Loop concat is O(N^2), N garbage objects
- StringBuilder = one mutable buffer, amortized O(N)
- toString() once at the end
- buildString { } is the Kotlin idiom (inline)
basics
~10 sKotlin Strings are immutable, so each += makes a brand-new String and copies everything. In a loop that repeats over and over. StringBuilder keeps one growing buffer and just appends, avoiding all those copies.
solid answer
~40 sIn Kotlin a `String` is immutable, so `s += part` does not mutate `s` — it allocates a new `String` and copies the old contents plus the new piece. Inside a loop of N iterations this is roughly O(N²) work and produces N throwaway objects, hammering the garbage collector. `StringBuilder` is a mutable character buffer: `append` writes into an internal array, only resizing (doubling) occasionally, so total work is amortized O(N). You build incrementally and call `toString()` once at the end. Kotlin's idiomatic wrapper is the `buildString { }` inline function, which creates a `StringBuilder`, runs your lambda with it as the receiver, and returns `toString()` for you. Use it for any non-trivial loop concatenation.
code
kotlin · 8 lines// Slow: quadratic, lots of garbage
var bad = ""
for (i in 1..1000) bad += i
// Fast: amortized linear
val good = buildString {
for (i in 1..1000) append(i)
}go deeper
States String is immutable and that StringBuilder avoids repeated copying; can write a basic append loop.
Quantifies O(N^2) vs amortized O(N), mentions GC pressure, and reaches for buildString as the idiom.
Explains capacity doubling/amortization precisely and notes the compiler already fuses single-expression concatenation, so only loops matter.
Frames it as a general mutable-accumulator pattern, discusses pre-sizing capacity and when micro-optimizing concatenation is premature vs. justified by profiling.
## The problem: String immutability In Kotlin, `String` is **immutable** — once created, its characters never change. The `+` / `+=` operators on strings do not modify anything in place; they create a **new** `String` whose contents are the concatenation. ```kotlin var s = "" for (i in 1..n) { s += i // allocates a NEW String each time, copying all previous chars } ``` Each `+=` copies the entire current contents into a fresh buffer. If the result grows to length N, iteration `k` copies ~k characters, so the total copying is `1 + 2 + ... + N ≈ N²/2` — **quadratic (O(N²))** time, plus N short-lived String objects for the garbage collector. ## The fix: StringBuilder `StringBuilder` is a **mutable** sequence of characters backed by a resizable array. `append` writes into that array; when the array fills, it grows (typically doubling capacity). Because doubling happens rarely, the *amortized* cost of N appends is **linear (O(N))**. ```kotlin val sb = StringBuilder() for (i in 1..n) { sb.append(i) } val result: String = sb.toString() // materialize once at the end ``` You mutate one object the whole time and convert to an immutable `String` exactly once via `toString()`. ## The idiomatic Kotlin form: buildString `buildString` is an **inline** stdlib function. It creates a `StringBuilder`, invokes your lambda with that builder as the **receiver** (so `this` is the builder and you call `append` directly), then returns `toString()`: ```kotlin val result = buildString { for (i in 1..n) append(i) // 'this' is the StringBuilder } ``` This is the preferred Kotlin idiom: it scopes the builder, removes the manual `toString()`, and because it is `inline` there is no lambda allocation overhead. ## Key terms - **Immutable**: state cannot change after construction. - **Amortized cost**: average cost per operation across many operations, smoothing out the occasional expensive resize. - **Receiver lambda**: a lambda whose `this` is set to a given object, letting you call its members without a qualifier.
- Is `"a" + "b" + "c"` on a single line also a problem?No. The compiler optimizes a fixed-length expression of constants/few operands (often into a single StringBuilder or a constant). The pathological case is repeated reassignment inside a loop.
- Does StringBuilder make the final String mutable?No. `toString()` produces a normal immutable String; the builder's mutability only exists during construction.
+= in a loop is like recopying an entire shopping list by hand each time you add one item; StringBuilder is just writing the next line on the same sheet.
saying these in an interview costs you the question
- Claiming Kotlin Strings are mutable
- Saying += in a loop is fine / O(N)
- Not knowing buildString exists
- Thinking StringBuilder returns a mutable string from toString()
- Calling toString() inside the loop on every iteration