Back to Blog
C#

C# String Interpolation: Syntax, Performance, and Edge Cases

c# string interpolation: Learn C# string interpolation syntax, format specifiers, compiled behavior, performance characteristics, and edge cases that affect production...

C#string interpolation.NETstring formattingperformanceculture
Illustration of C# string interpolation showing a formatted string with embedded expressions being resolved to output text.

C# string interpolation lets you embed expressions directly inside a string literal using the $ prefix. Instead of concatenating values or calling string.Format, you write $"Hello, {name}" and the compiler builds the final string for you. The syntax is straightforward, but the compiled behavior, formatting options, and performance characteristics matter when you use it in production code.

How String Interpolation Compiles

When the C# compiler encounters an interpolated string, it does not produce a single string at compile time. The generated code depends on the target framework and the complexity of the expression.

For a simple interpolated string like $"User {id} logged in", the compiler generates a call to string.Format when the string contains format items, or a direct string.Concat call when the interpolation is simple enough. In modern .NET, the compiler may instead use a DefaultInterpolatedStringHandler when the target type is string, which appends values into a reusable buffer and avoids intermediate string allocations.

This matters because the cost of building an interpolated string is not always identical to writing the literal by hand. Each embedded expression is evaluated, converted to a string, and appended. If the expression is expensive, that cost is paid every time the interpolated string is constructed.

string name = "Ada"; int year = 1843; string message = $"{name} published notes in {year}";

The compiler transforms this into code that formats each argument. The exact generated code varies by .NET version, but the observable result is the same: the expression values are converted and combined into one string.

Format Specifiers and Alignment

Interpolated strings support the same format specifiers as string.Format. Each interpolation expression can include a format string and an alignment value.

double ratio = 0.12345; string formatted = $"{ratio:P2}"; // 12.35% string aligned = $"{ratio,10:F2}"; // right-aligned, width 10

The alignment component is an integer that specifies the minimum width. Negative values left-align the content; positive values right-align it. The format component follows a colon and uses standard numeric, date, or custom format strings.

DateTime now = DateTime.UtcNow; string logLine = $"[{now:yyyy-MM-dd HH:mm:ss}] {level}: {message}";

This is useful for building log output, reports, or any text where consistent column widths matter. The format specifiers behave identically to string.Format, so existing format strings can be moved into interpolated strings without changes.

Interpolated String Handlers and Performance

In .NET 6 and later, the compiler can use interpolated string handlers to build strings more efficiently. A handler is a struct that receives the interpolated string's components and appends them into a buffer, often avoiding intermediate allocations.

public static void Log(string message) { // The compiler may use a handler that writes directly to the output Console.WriteLine($"[{DateTime.UtcNow:O}] {message}"); }

The default handler used for string targets is DefaultInterpolatedStringHandler. It holds a buffer and appends each part. This reduces the number of temporary strings compared to repeated concatenation.

You can also implement a custom interpolated string handler for types that need specialized formatting, such as writing directly to a Span<char> or a logging sink. This is an advanced feature, but it explains why interpolated strings can be faster than naive concatenation in hot paths.

The performance difference is not dramatic for occasional use, but in loops that build thousands of strings, the allocation reduction can be measurable. The exact numbers depend on the workload, so profile before optimizing.

Common Mistakes and Edge Cases

The most frequent mistake is forgetting that the expression inside the braces is evaluated eagerly. If the expression has side effects, they run every time the interpolated string is constructed.

int count = 0; string s = $"{count++}"; // increments count each time

Another issue is escaping braces. To include a literal { or } in an interpolated string, you must double them.

string json = $"{{\"name\": \"{name}\"}}";

This is easy to get wrong when generating JSON, templates, or any text that uses braces. The doubling rule applies only to the braces, not to other characters.

Conditional expressions inside interpolations require parentheses when the expression contains a colon and you also want a format specifier, because the colon is interpreted as the format separator.

string status = $"{condition ? "active" : "inactive"}"; // works string withFormat = $"{(condition ? "active" : "inactive"):X}"; // parentheses required

Culture and Globalization Behavior

Interpolated strings use the current culture by default. Numeric and date formatting depends on CultureInfo.CurrentCulture, which can produce different output on different machines.

double price = 1234.56; string us = $"{price:C}"; // $1,234.56 in en-US string de = $"{price:C}"; // 1.234,56 € in de-DE

If you need invariant or specific culture formatting, use the FormattableString type. An interpolated string assigned to FormattableString preserves the format and lets you call ToString(cultureInfo).

FormattableString fs = $"{price:C}"; string invariant = fs.ToString(CultureInfo.InvariantCulture);

This is important for logs, APIs, or any output that must be consistent across environments. Relying on the current culture can cause subtle bugs when the application runs on servers with different locale settings.

When to Use Interpolation vs Alternatives

String interpolation is the clearest option for most cases where you combine literal text with a few values. It reads better than concatenation and is less error-prone than string.Format because the placeholders appear inline with the values.

Concatenation with + is still useful when you are building a string incrementally in a loop, though StringBuilder is often better for that scenario. string.Format remains relevant when the format template is stored separately from the values, such as a resource file or a configuration setting.

string template = "Hello, {0}!"; string result = string.Format(template, name);

In that case, interpolation cannot be used because the template is not known at compile time. The choice depends on whether the format is fixed in code or comes from an external source.

Interpolated Strings in C# 10 and Later

C# 10 added the ability to use interpolated strings in constant expressions, but only when all interpolated values are constant strings.

const string Prefix = "Order"; const string Suffix = "Pending"; const string Status = $"{Prefix}-{Suffix}"; // valid in C# 10+

This is a small but useful feature for defining constant messages that include other constants. Earlier versions required concatenation for constant strings.

C# 11 introduced raw string literals, which can contain interpolations without escaping braces in most cases. This is helpful for generating code, JSON, or other brace-heavy content.

string json = $$""" { "name": "{{name}}", "active": {{active}} } """;

The number of $ signs determines how many braces are needed for an interpolation. This reduces the escaping noise that appears in older versions.