Why String Concatenation in a Loop Is Slow
Fast recall
Quick revision
Core answer
result += piece, each loop iteration creates another concatenated String and may copy the whole prefix built so far. For many similar-sized pieces, the repeated copying can approach quadratic work. Reuse one StringBuilder across the loop and call toString() after the last append.Interview-ready guide
Interview answer
String is immutable, so result += piece cannot extend the existing object. Each loop pass evaluates another concatenation and produces a String containing the old result followed by the new piece. The problem is not the + symbol by itself; it is rebuilding a result that becomes longer on every pass.
Imagine appending equal-sized pieces. The first pass copies a short result, the next pass copies a longer prefix, and later passes copy nearly everything already built. Those copied lengths form a growing sum. With many pieces, total copying can approach O(n²) in the final text length even though the final String itself contains only O(n) characters.
The usual fix is StringBuilder builder = new StringBuilder() before the loop, followed by builder.append(piece) inside it and builder.toString() at the end. The builder can reuse its mutable storage across appends. It may occasionally grow its capacity, but it does not rebuild the entire String prefix on every iteration.
For a short, one-off expression such as first + " " + last, + is usually the clearest code. Compile-time constants can be folded, and runtime concatenation is implemented by compiler and JVM machinery. The Java specification allows different strategies, so it is safer to explain the observable result and the loop cost than promise one exact temporary-object sequence.
Use a builder when code repeatedly assembles a growing value, especially in a loop or conditional sequence. If the expected final size is known, an initial capacity can reduce buffer growth. For important hot paths, measure with a proper benchmark because piece sizes, JDK optimizations, and surrounding work affect the actual speed.
Why String Concatenation in a Loop Is Slow
Direct Answer
A String cannot grow in place. With result += piece, each loop iteration creates another concatenated String and may copy the whole prefix built so far. For many similar-sized pieces, the repeated copying can approach quadratic work. Reuse one StringBuilder across the loop and call toString() after the last append.
Deep Dive & Architecture
Cumulative copying
The Java language defines a runtime String concatenation as producing a String that contains the left operand followed by the right operand. Because the result is immutable, a statement such as text = text + part cannot reserve more room inside the old String. The next value has to contain both the accumulated prefix and the new part.
If ten equal-sized pieces are added, the prefix sizes copied across the loop grow roughly like 0, 1, 2, and so on. The sum of those sizes grows quadratically with the number of pieces. Describing only “many temporary objects” misses the main point: object count grows linearly with loop iterations, while repeatedly copying the expanding prefix is what can make the total character work much larger.
Mutable capacity changes the pattern
StringBuilder represents a mutable sequence. append adds to the same logical builder, and its internal capacity is retained for later appends. Capacity occasionally has to grow and existing content may be copied during that growth, but growth is not required for every piece. This normally gives amortized linear work for assembling the final text. Calling toString() at the boundary produces the immutable value that the rest of the program can safely use.
Providing an estimated capacity can help when the output size is predictable, but guessing a very large value can waste memory. StringBuffer offers synchronized individual operations; it is not the default choice for a builder local to one thread.
Compiler and measurement boundaries
Constant concatenations such as "Java" + " 25" can be evaluated at compile time. For non-constant expressions, the JLS permits compilers to choose an implementation technique, and current javac versions can use StringConcatFactory rather than a literal source-level StringBuilder rewrite. That optimization can handle one expression well, but it does not change the source-level fact that assigning a newly concatenated growing String on every loop pass repeats the work.
Choose the readable + form for small expressions and a reusable builder for iterative assembly. If the code is performance-critical, compare implementations with JMH or application measurements instead of timing one loop with currentTimeMillis().
Code Example
import java.util.List;
class StringLoopDemo {
static String withPlus(List<String> parts) {
String result = "";
for (String part : parts) {
result += part;
}
return result;
}
static String withBuilder(List<String> parts) {
StringBuilder builder = new StringBuilder();
for (String part : parts) {
builder.append(part);
}
return builder.toString();
}
public static void main(String[] args) {
List<String> parts = List.of("Java", " ", "strings");
String slow = withPlus(parts);
String efficient = withBuilder(parts);
if (!slow.equals(efficient)) {
throw new AssertionError("The results should match");
}
System.out.println(efficient); // Java strings
}
}
Both methods have the same result. The difference is that withBuilder keeps one mutable builder across every loop iteration.
Check your understanding
Answer these without looking back at the article.
- Why can repeated String concatenation approach quadratic work?
- Why must the StringBuilder be created outside the loop?
- When is the String + operator still a good choice?
- When can an initial StringBuilder capacity help?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.