String Concat in Loop¶
Rule details
| Rule ID | string-concat-in-loop |
| Severity | WARNING — likely performance problem |
| Confidence | HIGH — structurally proven |
| Category | Loop amplifiers |
| Complexity | O(n²) → O(n) |
The problem¶
String.concat() inside a loop. Strings in Java (and Kotlin, and Groovy) are immutable — every concat() creates a brand new String and copies the entire accumulated content into it. On iteration 1, it copies 1 word. On iteration 100, it copies 100 words. On iteration 1,000, it copies 1,000 words. The total copies are 1 + 2 + 3 + ... + n = n(n+1)/2 — that's O(n²).
For 1,000 items averaging 20 characters each, that's roughly 10 million character copies instead of 20,000. StringBuilder appends in-place (amortized O(1) per append) and avoids the entire problem.
This is one of the oldest known Java performance pitfalls — it dates back to the JDK 1.0 era. Modern JVMs optimize + concatenation in some cases (JEP 280, invokedynamic-based string concat), but String.concat() calls still create a new object every time.
Where this shows up¶
- In report generators that build CSV or text output line by line
- In logging code that assembles multi-part messages inside loops
- In serialization utilities that convert collections to delimited strings
- In template engines or code generators that accumulate output
The pattern¶
The fix¶
Collectors.joining handles the separator cleanly:
buildString wraps a StringBuilder in an idiomatic Kotlin block:
Or use joinToString for a one-liner:
Suggestion variants¶
Use StringBuilder.append() — avoids copying the entire string on each iteration
Points to the standard fix: replace immutable concat() with a mutable StringBuilder.
When to ignore this¶
If the loop is bounded to a small constant (building a string from 3-5 items), the overhead is negligible and concat() reads more clearly than StringBuilder. Micro-benchmarks on modern JVMs show the crossover point around 10-20 iterations — below that, the StringBuilder overhead (object allocation, capacity management) can equal the copying cost.
Note¶
The more common += operator on strings (result += item) has the same O(n²) behavior but is not yet detected by this rule (requires parser-level support for assignment expressions). The String.concat() variant is detected. Collectors.joining() and joinToString() are always safe.
Related rules¶
Loop overhead cluster — patterns where per-iteration cost is higher than it looks:
- In-Loop Collection Building — similar pattern with collections instead of strings (building a list by copying on every iteration)
- Quadratic Removal — another O(n²) pattern from repeated shifting in array-backed lists
Redundancy cluster:
- Loop-Invariant Hoisting — when the string being concatenated is the same every iteration (constant part could be hoisted)
Language-specific examples:
- Java —
String.concat()in a loop with StringBuilder fix
Configuration¶
Disable globally:
Suppress inline: