Choosing Between StringBuilder and String Concatenation in C#
Choose concatenation or interpolation for small fixed inputs.
Learning outcome
You should be able to choose text construction based on the shape of the work, not on a blanket rule like “always use StringBuilder.” For a fixed, small number of pieces, concatenation or interpolation is usually clearer. For many appends in a loop, especially when the final size is unknown, StringBuilder is often a better fit. For collections, string.Join or string.Concat often expresses intent more directly than either manual concatenation or a builder.
Intuition
A string object's contents cannot change in place. Text construction produces the combined value without mutating its inputs, but it does not promise a fresh allocation for every expression: the compiler can fold constants and the library can reuse an existing string in no-op cases. Repeatedly growing a string in a loop can still create substantial intermediate allocation.
For example, when existing is a nonempty string, the current .NET runtime implementation of string.Concat(existing, string.Empty) returns existing. This illustrates an optimization, not a reference-identity contract to build application logic around. Immutability describes whether an object's contents can change; it does not count allocations.
Use this mental model:
- Few pieces, known up front: concatenation or interpolation.
- Collection of values:
string.Joinwhen you need separators, orstring.Concatwhen you do not. - Many incremental appends: consider
StringBuilder.
The official StringBuilder guidance recommends measuring meaningful workloads rather than automatically replacing ordinary string operations.
Deep dive
A single concatenation expression combines its operands into a string value without modifying them. Chaining a few operands can be perfectly reasonable, and the compiler can optimize some fixed expressions. Interpolation often wins for readability because the output's structure stays visible.
StringBuilder maintains mutable character storage. Appending extends that storage rather than requiring a complete immutable string result after every append. Capacity growth can still allocate, and ToString() materializes the immutable result. It is useful for text assembled in many steps, such as a report or generated document; it is not an allocation-free mechanism.
The key trade-off is:
- Concatenation/interpolation: simple code for short, fixed output.
- StringBuilder: mutable storage suited to repeated incremental construction.
In a growing concatenation loop, each nonempty addition may copy the accumulated prefix into another result. As the prefix gets longer, that repeated copying becomes costly. This differs from a single expression whose operands are all available together. Avoid turning the number of + symbols into a supposed allocation count.
The best choice still depends on fragment count and size, the runtime, and how the result is consumed. Measure elapsed time and allocations for representative inputs when performance matters.
Failure modes
Common mistakes include:
- using
StringBuilderfor two or three pieces where interpolation is clearer; - creating a new builder for every append instead of building the output in one appropriate scope;
- growing a string repeatedly in a hot loop without measuring the cost;
- manually managing separators when
string.Joinalready expresses the operation; - treating a tiny sample or a single benchmark as a universal rule;
- repeatedly calling
ToString()during construction when only the final result is needed.
If the consumer already accepts streamed output, writing directly to it may avoid buffering the entire document in a builder and then a large string. Choose this only when the workflow does not need the complete string in memory.
Interview drill
Answer these aloud:
- When would you choose interpolation over
StringBuilder? - Why is
string.Joinoften better than manually concatenating a list? - What makes repeated
+in a loop different from a single concatenation expression? - Why is “
StringBuilderis always faster” a bad interview answer? - What other concern besides speed should drive your choice?
Answer key
- Small, fixed assembly: interpolation keeps a short message's structure easy to read and avoids unnecessary builder setup. It is a sensible default when there are only a few known pieces.
- Collection intent:
Joinplaces the separator between values, so the code does not need its own first/last-element logic. UseConcatwhen no separator is wanted. - Repeated growth: each iteration depends on the accumulated result and may copy its prefix again. A single expression can be handled together; constant expressions can even be folded. These are different workloads.
- No universal winner: builder setup, capacity growth, final materialization, input size and runtime behavior all affect the result. Test representative workloads instead of claiming a fixed speedup.
- Clarity and memory use: choose maintainable code, preserve the required separators and formatting, and consider whether the full result needs to exist in memory or can be streamed.
Revision checklist
- I can explain string immutability without claiming every expression allocates.
- I start with
+or interpolation for small, fixed text assembly. - I consider
string.Joinfor delimited collections andstring.Concatfor undelimited collections. - I consider
StringBuilderfor many incremental appends. - I can explain why a builder still allocates and why repeated
ToString()calls can undermine its benefit. - I prefer clarity first, then measure the hot path.
Production code
The example below compares two styles of building the same report. It checks whether their results are equal for the supplied names. That is a useful consistency check for this input; it is not a performance benchmark or proof that both implementations are correct for all inputs.
Code walkthrough
The concatenation version starts with Names: and adds separators and names. For the supplied nonempty fragments, the report keeps growing, illustrating the repeated-concatenation workload. More generally, an empty append may be a no-op, so += is not a universal promise of a fresh allocation.
The StringBuilder version expresses the same logic through mutations of one builder, then calls ToString() once. A sensible initial capacity can reduce growth operations when the approximate final length is known; guessing an excessively large capacity can waste memory.
The first Console.WriteLine compares the two strings by value. The second prints the builder's result. To strengthen the example as an exercise, check an empty collection, a single name, empty names, and a much larger collection against independently specified expected formatting. These are suggested additional checks, not benchmark results.
Executable code examples
Compare concatenation and StringBuilder for the same output
Program.cs
using System.Text;
var names = new[] { "Ada", "Grace", "Linus" };
string concatenated = BuildWithConcatenation(names);
string built = BuildWithStringBuilder(names);
Console.WriteLine(concatenated == built ? "PASS" : "FAIL");
Console.WriteLine(built);
static string BuildWithConcatenation(string[] names)
{
string result = "Names:";
for (int i = 0; i < names.Length; i++)
{
result += i == 0 ? " " : ", ";
result += names[i];
}
return result;
}
static string BuildWithStringBuilder(string[] names)
{
var sb = new StringBuilder();
sb.Append("Names:");
for (int i = 0; i < names.Length; i++)
{
sb.Append(i == 0 ? " " : ", ");
sb.Append(names[i]);
}
return sb.ToString();
}Sources
- Microsoft Learn: StringBuilder explains mutable storage and workload-dependent performance guidance.
- Microsoft Learn: String.Join documents combining collection elements with a separator.
- Microsoft Learn: String.Concat documents concatenation without separators.
- .NET runtime: String.Manipulation.cs shows the current two-string Concat implementation, including null/empty fast paths. This source illustrates implementation behavior and can change between runtime versions.
TechInterviewNotes Editorial Team · Updated 25 Oct 2026 ·