skip to content

What does the stdlib `buildString { ... }` function do, and how is it different from concatenating strings with `+` in a loop?

level: juniorimportance: must knowfreq 70%

answer

  1. StringBuilder receiver lambda, returns String
  2. inline; equals StringBuilder().apply{}.toString()
  3. append/appendLine inside, this is the builder
  4. O(n) vs O(n^2) for + in a loop
  5. capacity overload pre-sizes the buffer

basics

~20 s

buildString gives you a temporary text buffer you fill inside a lambda, then hands back the finished String. It reuses one buffer, so it's faster than + in a loop, which makes a brand-new string each time.

solid answer

~40 s

`buildString` is an inline stdlib function that creates a `StringBuilder`, runs the lambda you pass with that builder as the receiver (`this`), then calls `toString()` and returns the resulting immutable `String`. Inside the block you call `append`, `appendLine`, `insert`, etc. directly. It is equivalent to `StringBuilder().apply { ... }.toString()` but more concise and clearly intent-revealing. The key advantage over `str + x` in a loop is that string concatenation creates a new `String` object on every iteration (O(n^2) work and garbage), whereas `buildString` mutates a single growable char buffer (amortized O(n)). An overload `buildString(capacity) { ... }` pre-sizes the `StringBuilder` to avoid array resizing when you know the rough output length.

code

kotlin · 7 lines
kotlin
val report = buildString {
    append("Total: ")
    append(42)
    appendLine()
    append("Done")
}
// report == "Total: 42\nDone"

go deeper

for a junior

Knows it builds a String via a StringBuilder lambda and is preferable to + in loops.

for a middle

Explains the receiver lambda, the inline nature, and the O(n) vs O(n^2) cost difference.

for a senior

Mentions the capacity overload, garbage/allocation implications, and the apply().toString() equivalence.

for a principal

Discusses when JVM string concatenation is already optimized (single expression via invokedynamic) vs loops where buildString matters, and capacity tuning for hot paths.

## What `buildString` is `buildString` is a function in the Kotlin standard library with this shape: ```kotlin public inline fun buildString(builderAction: StringBuilder.() -> Unit): String { return StringBuilder().apply(builderAction).toString() } ``` Key terms: - **`inline`**: the compiler copies the function body (and your lambda body) into the call site, so there is no lambda object allocated and no extra call overhead. - **`StringBuilder.() -> Unit`**: this is a *receiver lambda* (a.k.a. function literal with receiver). Inside the `{ ... }` block, `this` is a `StringBuilder`, so you can call its methods like `append` without qualifying them. - **`StringBuilder`**: a mutable, growable buffer of characters (backed by a resizable char array). You add to it; it is not immutable like `String`. ## How you use it ```kotlin val csv = buildString { append("id,name") appendLine() // adds a line separator for (i in 1..3) { append(i).append(',').append("row").append(i).appendLine() } } ``` Inside the block you have the full `StringBuilder` API: `append`, `appendLine`, `insert`, `deleteCharAt`, `setLength`, indexing, etc. When the block finishes, `buildString` calls `toString()` once and returns an ordinary immutable `String`. ## Why it beats `+` in a loop `String` in Kotlin/JVM is immutable. Writing `result = result + x` allocates a **new** `String` each iteration and copies all existing characters, so building an n-character string costs roughly O(n^2) time and produces lots of garbage. `buildString` mutates one underlying char array that grows geometrically, giving amortized O(n). ## Pre-sizing There is an overload that takes an initial capacity: ```kotlin val s = buildString(capacity = 1024) { repeat(100) { append("x") } } ``` Passing a good `capacity` avoids internal array reallocations when you already know roughly how big the output will be. ## Mental model `buildString { ... }` == `StringBuilder().apply { ... }.toString()` — same result, less ceremony, clearer intent.

  • Is the lambda parameter or receiver inside `buildString`?
    It's a receiver: the block's type is `StringBuilder.() -> Unit`, so `this` is the `StringBuilder` and you call `append(...)` unqualified.
  • What's returned from the block — the StringBuilder or a String?
    A `String`. The block returns `Unit`; `buildString` itself calls `toString()` and returns the immutable `String`.

It's a scratch pad you scribble on, then photocopy once and throw the pad away.

saying these in an interview costs you the question

  • Thinking `buildString` returns a mutable `StringBuilder`
  • Claiming `+` concatenation in a loop is just as efficient
  • Trying to `return` a value from the block (it's `Unit`)
  • Not knowing `this` inside the block is the `StringBuilder`

context