String Formatting

Raw numbers rarely look right on screen: you want two decimal places for prices, thousands separators for big numbers, percentages, padded columns and dates. C# handles all of this with format specifiers inside interpolated strings, and it adapts the result to the user's culture.

Format specifiers

Inside an interpolation hole, add a colon and a format after the value: {value:format}.

C#
double ratio = 0.4567;
double big = 1234567.891;
decimal price = 1299.5m;
int id = 42;

Console.WriteLine($"{big:N2}");      // number with separators
Console.WriteLine($"{big:N0}");
Console.WriteLine($"{ratio:F1}");    // fixed-point, 1 decimal
Console.WriteLine($"{ratio:P1}");    // percentage
Console.WriteLine($"{price:C}");     // currency
Console.WriteLine($"{id:D5}");       // integer padded with zeros
Console.WriteLine($"{255:X}");       // hexadecimal
Console.WriteLine($"{big:E2}");      // scientific
Output
1,234,567.89
1,234,568
0.5
45.7%
$1,299.50
00042
FF
1.23E+006
SpecifierNameExampleResult (en-US)
NnNumber{1234.5:N2}1,234.50
FnFixed-point{1234.5:F3}1234.500
CnCurrency{9.5:C}$9.50
PnPercent (×100){0.25:P0}25%
DnDecimal integer{7:D3}007
XnHexadecimal{255:X4}00FF
EnScientific{1500.0:E1}1.5E+003

The number after the letter is the precision: decimal places for N, F, C, P and E, or minimum digits for D and X.

Custom formats

When the standard specifiers don't fit, build a pattern: 0 is a required digit, # an optional one, , a thousands separator and . the decimal point. Text in quotes appears as-is:

C#
double value = 1234.5;

Console.WriteLine($"{value:0.00}");
Console.WriteLine($"{value:#,##0.0}");
Console.WriteLine($"{0.5:0.###}");
Console.WriteLine($"{7:000}");
Console.WriteLine($"{-3.2:+0.0;-0.0;zero}");   // positive;negative;zero sections
Console.WriteLine($"{0:+0.0;-0.0;zero}");
Console.WriteLine($"{98.6:0.0' °F'}");
Output
1234.50
1,234.5
0.5
007
-3.2
zero
98.6 °F

Alignment

A comma and a number after the value set a minimum width: positive right-aligns, negative left-aligns. Combined with a format, it makes tidy columns:

C#
var rows = new (string Item, int Qty, decimal Price)[]
{
    ("Keyboard", 1, 49.99m),
    ("Mouse", 2, 19.5m),
    ("USB cable", 10, 3.25m),
};

Console.WriteLine($"{"Item",-12}{"Qty",5}{"Total",10}");
Console.WriteLine(new string('-', 27));
foreach (var (item, qty, price) in rows)
{
    Console.WriteLine($"{item,-12}{qty,5}{qty * price,10:F2}");
}
Output
Item          Qty     Total
---------------------------
Keyboard        1     49.99
Mouse           2     39.00
USB cable      10     32.50

The full form of a hole is {value,alignment:format}. (The (string Item, int Qty, decimal Price) type is a tuple — lesson 40 shows similar data with records.)

Expressions and braces

Any expression can go in the braces, including method calls and the conditional operator (wrap it in parentheses because of the colon). To print a literal brace, double it:

C#
int count = 3;
string[] names = ["Ana", "Ben", "Cy"];

Console.WriteLine($"{count} file{(count == 1 ? "" : "s")} selected");
Console.WriteLine($"Names: {string.Join(", ", names)} ({names.Length})");
Console.WriteLine($"{{ braces }} and a value: {count * 2}");
Output
3 files selected
Names: Ana, Ben, Cy (3)
{ braces } and a value: 6

Culture

Formats follow the current culture — the user's language and region settings — so the same code prints 1,234.50 in the US and 1.234,50 in Germany. Pass a specific culture to ToString, or use string.Create for a whole interpolated string:

C#
using System.Globalization;

decimal amount = 1234.5m;
var de = new CultureInfo("de-DE");
var ja = new CultureInfo("ja-JP");

Console.WriteLine(amount.ToString("C", de));
Console.WriteLine(amount.ToString("C", ja));
Console.WriteLine(amount.ToString("N2", CultureInfo.InvariantCulture));
Console.WriteLine(string.Create(de, $"Total: {amount:N2}"));
Output
1.234,50 €
¥1,235
1,234.50
Total: 1.234,50

Show users their own culture. For log files, file formats and data sent to other programs, use CultureInfo.InvariantCulture so the output is stable everywhere. (The examples in this course run with the US English culture.)

string.Format and composite formatting

Older code uses string.Format with numbered placeholders. The format rules inside the braces are the same:

C#
string s = string.Format("{0} scored {1:P0} ({2}/{3})", "Lee", 0.875, 35, 40);
Console.WriteLine(s);
Console.WriteLine("{0,-6}|{1,6}|", "left", "right");
Output
Lee scored 88% (35/40)
left  | right|

Console.WriteLine accepts the same placeholder style directly. Prefer interpolation in new code — it's easier to read and the compiler checks the expressions.

Formatting dates

Dates use their own specifiers, covered properly in lesson 56. A taste:

C#
var launch = new DateTime(2026, 3, 14, 9, 5, 0);

Console.WriteLine($"{launch:yyyy-MM-dd}");
Console.WriteLine($"{launch:dd/MM/yyyy HH:mm}");
Console.WriteLine($"{launch:dddd, MMMM d}");
Console.WriteLine($"{launch:d}");
Console.WriteLine($"{launch:t}");
Output
2026-03-14
14/03/2026 09:05
Saturday, March 14
3/14/2026
9:05 AM

Try it yourself

Print a formatted receipt. Try changing the culture by adding using System.Globalization; and CultureInfo.CurrentCulture = new CultureInfo("fr-FR"); at the top.

Try it yourself · Program.cs
Edit the code, copy it into Program.cs and run it with dotnet run Program.cs (.NET 10 or later). How to set up .NET
Output
Sandwich     $6.50
Juice        $2.75
Cookie       $1.80
==================
Subtotal    $11.05
Tax 8%       $0.88
Total       $11.93