String interpolation builds a string from literal text and expressions. Prefix the literal with $, put any C# expression in braces, and its value is converted to text and inserted:
Output:
Hello, Maya!
3 items at 4.5 = 13.5
Name has 4 letters, upper: MAYA
Free shipping: yes
Interpolation arrived in C# 6. The compiler turns it into a string.Format or string.Concat call (or, since C# 10, into more efficient handler code), so it is not slower than building the string by hand.
The conditional expression in the last line needs parentheses. Inside an interpolation hole, a colon starts a format specifier, so {x > 10 ? "yes" : "no"} without parentheses is a compile error.
Format specifiers
After the expression, a colon and a format string control how the value is written. The same format strings work with ToString("...") and string.Format.
Output:
1234567.89
1,234,567.89
1,234,568
00042
FF
00ff
1.23E+006
0.813
81.3%
1,234,567.89
007.5
3.1
The standard codes, each optionally followed by a precision number:
| Code | Name | Example | Result |
|---|---|---|---|
F or F2 | Fixed-point | {3.14159:F2} | 3.14 |
N or N0 | Number with group separators | {1234.56:N0} | 1,235 |
C | Currency (culture's symbol and pattern) | {9.5:C} in en-US | $9.50 |
D5 | Integer padded with zeros | {42:D5} | 00042 |
X, x | Hexadecimal | {255:X} | FF |
E2 | Scientific | {1234.5:E2} | 1.23E+003 |
P1 | Percent (multiplies by 100) | {0.256:P1} in en-US | 25.6% |
G | General (shortest) | {2.50m:G} | 2.50 |
R | Round-trip (double) | {0.1:R} | 0.1 |
Custom patterns build a format from placeholders: 0 is a digit that is always shown, # is a digit shown only if significant, . is the decimal point, , inside the number enables group separators, and % multiplies by 100. "0.##" shows up to two decimals and drops trailing zeros; "#,##0.00" is the classic accounting format.
All numeric formatting rounds the displayed value; the variable itself is unchanged. When a value sits exactly halfway (2.25 shown with one decimal), different runtimes and types can round the tie differently, so if the rule matters, round explicitly first with Math.Round(value, 1, MidpointRounding.AwayFromZero) and then format.
Dates in interpolation
DateTime values take date format strings the same way:
Output:
2026-09-24
24/09/2026 16:05
Thu, Sep 24
Shipped at 16:05 on Thursday
ETA 02:10
2.2 hours
A TimeSpan custom format requires literal characters to be escaped with a backslash, which in a regular string literal is written \\:. The full list of date codes (yyyy, MM, HH, tt...) and the mistakes people make with them (mm is minutes, MM is months) are on DateTime format.
Alignment and padding
A comma after the expression sets a minimum field width. A positive number right-aligns, a negative number left-aligns. Combined with a format, it lines up tables in plain text:
Output:
Item | Price| Sold
----------------------------
Espresso | 2.40| 118
Latte | 3.60| 64
Hot chocolate | 4.25| 9
The syntax is {expression,alignment:format}: alignment first, then format. A value longer than the width is not truncated; it pushes the rest of the line over. PadLeft and PadRight do the same padding outside interpolation.
Escaping braces
To print a literal brace, double it: {{ and }}. This comes up when the output is JSON, CSS or code:
Output:
{ "id": 42, "name": "Ana" }
Set literal: {1, 2, 3}
{42}
Building JSON by hand like this is fine for a quick log line; for real JSON use a serializer, which handles quotes and special characters inside name correctly.
string.Format and composite formatting
Before C# 6, formatting was done with string.Format, which takes numbered placeholders and a list of arguments. The same composite format syntax is accepted by Console.WriteLine, StringBuilder.AppendFormat and TextWriter.Write:
Output:
Notebook x4: 14.00
Notebook | 3.50
echo echo !
Dear Leo, your order #000731 has shipped.
Prefer interpolation in code: the values sit where they appear, so a placeholder cannot point at the wrong argument. string.Format remains the right tool when the template is data, such as a translated message loaded from a resource file. A placeholder index with no matching argument ({2} with two arguments) throws a FormatException at run time.
Culture: controlling separators and currency
Interpolation, ToString and string.Format all use the current culture of the thread by default. On a machine set to German, {1234.5:N2} produces 1.234,50; in the US it produces 1,234.50. The currency format C uses the culture's symbol. To choose the culture explicitly, capture the interpolated string as a FormattableString and format it with a provider:
Output:
Total: 1,234.50
Total: 1.234,50
$1,234.50
1.234,50
1234.5,0.75
Use the user's culture for text people read, and CultureInfo.InvariantCulture for anything another program reads. A CSV written with the current culture on a German machine has commas inside numbers and is unreadable elsewhere. On .NET 6 and later, string.Create(CultureInfo.InvariantCulture, $"...") does the same as FormattableString.Invariant without the intermediate object.
Verbatim and raw interpolated strings
$ combines with @ for a verbatim interpolated string: backslashes are literal and the string may span lines. C# 8 and later accept either order ($@ or @$); C# 6 and 7 require $@:
Output:
C:\Users\ana\Reports\2026\summary.txt
Report for ana
Year: 2026
C# 11 added raw interpolated strings. With one $, braces mark expressions as usual. With $$, a single brace is a literal and expressions need two braces, which makes JSON and code templates readable:
// C# 11 and later
int id = 42;
string name = "Ana";
string json = $$"""
{
"id": {{id}},
"name": "{{name}}"
}
""";
C# 11 also allows newlines inside an interpolation hole, so a long expression or a switch expression can be split over several lines.
Common mistakes
- Forgetting the
$."Total: {total}"prints the braces literally. The compiler does not warn. - A ternary without parentheses inside the braces: the colon is read as a format separator.
- Formatting machine-readable output with the current culture. Decimal commas break CSV, JSON and SQL. Use the invariant culture.
- Using
ToString()without a format for money.2.5mprints as2.5, not2.50. UseF2,N2orC. - Building large strings with interpolation in a loop. Each iteration creates a new string; use StringBuilder or
string.Join.
Frequently Asked Questions
What is string interpolation in C#?
A string literal prefixed with $ in which expressions inside braces are evaluated and inserted: $"Total: {price * qty}". It was added in C# 6 and replaces most uses of string.Format and + concatenation. Any expression works inside the braces, including method calls and property access.
How do I format a number to 2 decimal places in C#?
Add a format specifier after a colon: $"{price:F2}" prints 2 digits after the decimal point, and $"{price:N2}" does the same with thousands separators. Outside interpolation, the same codes work with price.ToString("F2"). Both round the value for display without changing it.
How do I escape braces in a C# interpolated string?
Double them: {{ prints { and }} prints }. For example $"{{ \"id\": {id} }}" prints { "id": 42 }. In C# 11 raw interpolated strings, you can instead start the literal with $$ so that single braces are literal and {{expr}} marks an expression.
What is the difference between string.Format and string interpolation?
They produce the same result with the same format codes. string.Format("{0} costs {1:F2}", name, price) refers to arguments by position; $"{name} costs {price:F2}" puts the expressions inline, so it is easier to read and cannot get the argument order wrong. string.Format is still needed when the format string itself comes from a resource file or a database.
Why does my interpolated number show a comma instead of a period?
Interpolation formats with the current culture of the thread, and many cultures use a comma as the decimal separator. For output read by machines (files, JSON, URLs), format with the invariant culture: FormattableString.Invariant($"{value:F2}"), or on .NET 6 and later string.Create(CultureInfo.InvariantCulture, $"...").