C# Interpolated String Syntax and Usage
c# interpolated string: Understand C# interpolated string syntax, formatting options, alignment, culture handling, and performance considerations for safer, cleaner st...
C# interpolated strings, introduced in C# 6, let you embed expressions directly into string literals using the $ character. Instead of concatenating values with + or using composite formatting like string.Format, you write the expression inline, and the compiler transforms it into a String.Format call or a more efficient form depending on the target. This makes the code easier to read and less error-prone, but the convenience comes with some details you should understand to avoid common pitfalls and to write code that performs well.
How an Interpolated String Compiles
When you write an interpolated string in C#, the compiler does not simply produce a string at compile time. It generates code that evaluates each interpolation expression at runtime and then formats them into a resulting string. The exact code depends on the context and the target type. In most cases, the compiler emits a call to string.Format, passing the format string and the evaluated expressions as arguments. For example:
string name = "Ada"; int year = 1815; string message = $"{name} was born in {year}.";
This compiles to something equivalent to:
string message = string.Format("{0} was born in {1}.", name, year);
Understanding this behavior is useful because it explains why an interpolated string creates a new string object every time it is evaluated, and why the expressions are evaluated each time the line runs. It also clarifies why you can use any expression inside the braces, including method calls, property accesses, and conditional expressions, as long as the expression is valid C#.
Formatting Interpolation Expressions
You can control how an interpolated value appears by adding a format string after a colon inside the braces. The syntax is {expression:format}. The format string follows the same rules as the format specifier in string.Format. For example, to format a decimal number as currency or a date as a short date:
decimal price = 19.99m; DateTime today = DateTime.Today; string pricedText = $"Price: {price:C}"; // Price: $19.99 (depending on culture) string dateText = $"Date: {today:d}"; // Date: 6/16/2025 (US culture)
The format specifier is only applied to certain types, such as numeric types, dates, and TimeSpan. If you apply a format specifier to a type that does not support it, the compiler will not complain, but at runtime you will get a FormatException because string.Format cannot interpret the format. So be careful: {someObject:XX} will throw if someObject does not implement a suitable IFormattable interface.
Formatting also respects the current culture by default. If you need a specific culture, you must use the IFormattable overload, which we will cover later.
Alignment and Padding
In composite formatting, you can specify a minimum width and alignment with a comma and a positive or negative number. The same applies to interpolated strings: {expression, alignment}. A negative alignment left-aligns the value, and a positive number right-aligns it. This is handy for creating aligned columns in reports or console output:
string[] names = { "Alice", "Bob", "Charlie" }; int[] scores = { 98, 85, 92 }; for (int i = 0; i < names.Length; i++) { Console.WriteLine($"{names[i],-10} {scores[i],5}"); }
The -10 left-aligns the name in a field of width 10, and 5 right-aligns the score. The alignment value is a minimum width; if the value is longer, it is not truncated.
Combining alignment and format specifier is also possible: {expression, alignment:format}. For instance, {score,10:N2} would right-align a number with two decimal places in a field of width 10.
Escaping Braces and Literal Text
To include a literal { or } character in an interpolated string, you must double it. This is because the single brace is used to delimit interpolation expressions. For example:
string json = $"{{\"name\": \"{name}\"}}";
The double braces {{ and }} produce a single brace in the output. This escaping is necessary for any literal brace, even if it is not part of an interpolation expression. This rule is easy to forget and often leads to compile-time errors, which is actually helpful because the compiler catches it.
Also note that the colon and comma have special meanings inside the braces and cannot be used in the expression part without being interpreted as the format or alignment separator. If your expression contains a colon, such as a conditional operator, you must wrap the expression in parentheses: {(condition ? "yes" : "no")}.
Using Interpolated Strings as IFormattable
In most code, you assign an interpolated string to a string variable or pass it to a method that expects a string, and the compiler generates a string.Format call that uses the current culture. However, you can also use the FormattableString type to defer formatting and control culture. The FormattableString class is a representation of the interpolated string that you can convert to a string using ToString(IFormatProvider). This is particularly useful when you want to format dates and numbers consistently across different locales. For example:
CultureInfo culture = CultureInfo.GetCultureInfo("fr-FR"); FormattableString message = $"Price: {price:C} Date: {today:d}"; string localizedMessage = message.ToString(culture);
Here, the interpolated string is assigned to a FormattableString rather than a string. The compiler generates code that stores the format string and the arguments, allowing you to call ToString with a specific culture later. This is a powerful technique for localization, but it does carry a small overhead because it stores the arguments in an object array.
The compiler chooses string.Format by default unless the target type is FormattableString or IFormattable. By casting explicitly to IFormattable, you can also call ToString(string format, IFormatProvider formatProvider) directly:
IFormattable msg = $"Price: {price:C}"; string s = msg.ToString(null, culture);
Common Mistakes with Interpolated Strings
One common mistake is assuming that interpolated strings are compile-time constants. They are not, because the expression values are known only at runtime. You cannot use an interpolated string as a constant, such as in a const field or an attribute parameter. If you need a constant, you must use a regular string literal or a const with concatenation (though that is limited to string literals only).
Another pitfall is the misuse of the $ and @ prefixes. An interpolated string can be combined with a verbatim string by placing $ before @ (or @ before $ in newer versions, but the conventional order is $@"..."). The verbatim interpolated string treats backslashes as literal characters and allows the string to span multiple lines, but braces still need escaping. For example:
string path = $@"C:\Users\{name}\Documents";
The @ makes the backslashes literal, so you don't need to double them in the file path.
Performance and Allocation Considerations
Every interpolated string that is assigned to a string allocates a new string object. If you have a loop that builds large strings, using interpolated strings repeatedly can create many intermediate strings and cause memory pressure. For example, concatenating many rows in a report with string result = ""; and result += $"{row}\n"; is inefficient because each += creates a new string. Instead, use StringBuilder when you are building a large amount of textual data in a loop:
var sb = new StringBuilder(); foreach (var row in rows) { sb.AppendLine($"{row.Name}: {row.Value}"); } string result = sb.ToString();
This still uses interpolated string syntax for each row, but the AppendLine method writes directly into the StringBuilder without creating an intermediate string for the entire line? Actually, AppendLine accepts a string and appends it, but the interpolated string is still materialized as a string before being appended. To avoid that, you can use AppendFormat or, in .NET 6+, use AppendInterpolatedString which passes the DefaultInterpolatedStringHandler directly to the builder, avoiding the intermediate string.
In .NET 6 and later, the compiler uses a DefaultInterpolatedStringHandler struct to build strings more efficiently, especially in hot paths. This handler can write directly into a StringBuilder or a Span<char> buffer without creating an intermediate string. The compiler chooses this handler when the target is a string and the code is running on .NET 6 or later. This means that in modern .NET, interpolated strings are already more efficient than older string.Format calls, but they still allocate the final string.
If you are writing a high-performance method that builds a string, you can take advantage of the interpolated string handler pattern by creating your own handler type. This is an advanced topic, but the key point is that the compiler generates code that can be customized, and many libraries use this to improve performance.
Culture and Globalization
By default, an interpolated string uses the current culture (CultureInfo.CurrentCulture) for formatting numbers and dates. This is usually what you want for user-facing display, but it can cause issues when you need a consistent format, such as in log files or API responses. To force a specific culture, you can use the FormattableString approach described earlier, or you can use the string.Create(IFormatProvider, DefaultInterpolatedStringHandler) method which is available in .NET 6+ and lets you specify a provider directly:
string s = string.Create(CultureInfo.InvariantCulture, $"{value:N2}");
The compiler recognizes this pattern and uses the supplied provider for all formatting operations within the interpolation expression. This is cleaner than calling ToString on each value manually.
When to Use Interpolated Strings vs. Alternatives
Interpolated strings are the preferred way to build short, readable strings with embedded values. They are not appropriate for storing format templates that are reused many times because the format string is evaluated each time. If you need a reusable template, you might store a constant format string and use string.Format or FormatInterpolatedString with a FormattableString that captures the arguments, but honestly, the difference is small for most applications.
For building large or complex output, such as generating HTML or CSV files, a StringBuilder with AppendFormat or interpolated strings is more flexible. However, if you are generating the same format with different values many times, consider pre-compiling a FormattableString and reusing it? Actually, FormattableString still holds the format and arguments, but you can store it and call ToString with different cultures, but you cannot change the arguments. So reusing the same FormattableString with different arguments isn't possible; you'd need a new instance each time.
Edge Case: Interpolated Strings as ReadOnlySpan<char>
You cannot directly assign an interpolated string to a ReadOnlySpan<char> because the compiler needs to create a temporary string. In .NET 6+, there is an overload string.Create(IFormatProvider, ref DefaultInterpolatedStringHandler) that can write into a Span<char>, but that is advanced. In normal code, you rarely need this.
Another corner case: interpolated strings in attributes or constants are not allowed because they require runtime evaluation. If you need a constant that looks like an interpolated string, you must use a regular string constant and concatenate the static parts.
Choosing the Right Formatting Approach
| Scenario | Recommended Approach |
|---|---|
| Short, one-time string with a few values | Interpolated string ($"...") |
| Building many lines in a loop | StringBuilder with interpolated append |
| Formatting for a specific culture | FormattableString or string.Create |
| Constant format template | Regular string literal |
| High-performance, many iterations | Interpolated string handler pattern |
Use interpolated strings by default because they are readable and safe. If profiling shows that a specific piece of code is a bottleneck, consider StringBuilder or custom handler optimization.
Conclusion
C# interpolated strings are a powerful, readable way to build strings with embedded expressions. By understanding how they compile, how to format and align values, and the effect on performance and culture, you can use them effectively and avoid common mistakes. Prefer interpolated strings over string.Format or concatenation for most code due to their clarity and low risk of format mistakes, but always be aware of allocation costs when building large outputs. The remainder of this article demonstrates how to handle the most typical scenarios in production code.
Production Example: Log Entry Builder
A common practical use is building a log entry with timestamp and severity. Interpolated strings make the code compact:
public void Log(LogLevel level, string message) { string entry = $"{DateTime.UtcNow.ToString("o")} [{level}] {message}"; logger.WriteLine(entry); }
Note that DateTime.UtcNow is evaluated when the interpolated string is constructed, not when the log line is actually written. If you queue log entries, you should capture the time as a variable first if you need the exact time of entry creation.
Handling Null Values
If an interpolated expression evaluates to null, it is replaced with an empty string, not the text "null". This is often what you want, but if you need a placeholder, you must be explicit: {value ?? "N/A"}. This is a common subtlety.
Advanced: Custom Interpolated String Handler
For the rare case where you need to avoid allocations entirely, you can implement your own InterpolatedStringHandler attribute and handler type. This is an advanced optimization used in high-performance libraries, but the pattern is based on the DefaultInterpolatedStringHandler provided by the runtime. The compiler recognizes the [InterpolatedStringHandler] attribute on a constructor and calls it with the literal parts and arguments. Implementing a custom handler is beyond the scope of this article, but you can search for the InterpolatedStringHandlerAttribute to learn more. In practice, most applications do not need this level of optimization.
Interpolated strings are a cornerstone of modern C# development. By mastering their syntax and behavior, you can write clearer, more maintainable code that is less prone to formatting bugs. The next time you reach for string.Format, consider whether an interpolated string would be simpler.