Strings are immutable, so every += creates a new String and copies all the characters built so far. In a loop of n iterations that is O(n²) character copying, plus many temporary objects. A StringBuilder appends into one growing buffer, which is O(n) overall.
Key Points: • Outside loops, + is fine. Since Java 9 the compiler turns a single expression such as a + b + c into an efficient invokedynamic call (StringConcatFactory). • Inside a loop, each iteration still creates a new String, because the compiler can't merge separate statements. • StringBuilder is not synchronized and is the right choice in almost all code. StringBuffer is the synchronized, slower version. • Give StringBuilder an initial capacity when you know the approximate size, to avoid buffer resizing. • For joining collections, String.join() or Collectors.joining() are clearer than a manual loop.
Code Example:
public class ConcatPerformance {
public static void main(String[] args) {
int n = 20_000;
long t1 = System.nanoTime();
String s = "";
for (int i = 0; i < n; i++) {
s += i; // copies the whole string every time
}
long plusMs = (System.nanoTime() - t1) / 1_000_000;
long t2 = System.nanoTime();
StringBuilder sb = new StringBuilder(n * 5);
for (int i = 0; i < n; i++) {
sb.append(i); // appends into one buffer
}
String built = sb.toString();
long sbMs = (System.nanoTime() - t2) / 1_000_000;
System.out.println("Same result: " + s.equals(built));
System.out.println("Length: " + built.length());
System.out.println("StringBuilder at least 10x faster: " + (plusMs >= sbMs * 10));
}
}Output:
Same result: true Length: 88890 StringBuilder at least 10x faster: true
Good to Know: The example prints a comparison instead of raw milliseconds, because timings vary by machine. On a typical laptop the += loop takes hundreds of milliseconds, while StringBuilder takes about one millisecond. For precise numbers, use JMH.
Interview Tip: A concise interview answer is:
"Because Strings are immutable, each += in a loop creates a new String and copies everything built so far, which is O(n²). StringBuilder appends into one resizable buffer, so it's O(n). A single expression like a + b + c is fine, since the compiler optimises it, but inside loops I use StringBuilder, or String.join and Collectors.joining for collections."
Previous question: What does String.intern() do, and when should you use it?
Next question: What is the difference between String.valueOf(), toString() and concatenating with "" when converting to a String?