C# Optional Parameters
Optional parameters let a C# method provide default values for arguments the caller leaves out. They matter because they keep common calls short while still allowing callers to customize less common details.
Used well, optional parameters make APIs easier to read. Used carelessly, they can hide meaning, create confusing overloads, or cause versioning surprises when a default value changes later.
Overview: How Optional Parameters Work
An optional parameter is a method parameter with a default value in its declaration. For example, static void PrintMessage(string text, bool uppercase = false) has one required parameter and one optional parameter. A caller can write PrintMessage("hello"), and the compiler treats the omitted uppercase argument as false.
Optional parameters are a compile-time feature. The method itself still has the full parameter list. The important detail is that the compiler fills in missing arguments at each call site. After compilation, a call that omitted an optional argument behaves as though the default value had been written explicitly in the source code.
This means default values are not looked up dynamically each time the program runs. If a library changes an optional parameter default from false to true, already compiled callers may keep using the old value until they are recompiled. This is one reason public libraries often prefer overloads for defaults that might change.
Optional parameters must come after all required parameters in the same parameter list. This rule keeps positional calls understandable. If role = "Member" came before a required name, a call with one string would be unclear: is the string the role or the name?
Default values must be compile-time constants, null, default, or certain simple values that the compiler can embed. You can write defaults such as 0, false, "General", null, and DayOfWeek.Monday. You cannot use a runtime expression such as DateTime.Now, new Random(), or a method call as an optional parameter default.
Optional parameters often work together with named arguments. Named arguments let callers skip an earlier optional parameter and supply a later one by name, such as CreateReport("Sales", includeTotals: true). Without names, positional arguments must be supplied in order.
Syntax
static returnType MethodName(requiredType requiredName, optionalType optionalName = defaultValue)
{
statements;
}
MethodName(requiredValue);
MethodName(requiredValue, customOptionalValue);
MethodName(requiredName: requiredValue, optionalName: customOptionalValue);
| Part | Meaning |
|---|---|
requiredType requiredName |
A normal required parameter. The caller must provide an argument. |
optionalType optionalName = defaultValue |
An optional parameter. The caller may omit it, and the compiler inserts the default value. |
defaultValue |
A compile-time value such as a number, string literal, false, null, default, or enum value. |
optionalName: |
A named argument. It makes the call clearer and can let you skip other optional parameters. |
Examples
A Simple Optional Parameter
using System;
class Program
{
static void Main()
{
PrintStatus("Build started");
PrintStatus("Build failed", urgent: true);
}
static void PrintStatus(string message, bool urgent = false)
{
string prefix = urgent ? "URGENT" : "INFO";
Console.WriteLine($"[{prefix}] {message}");
}
}
Output:
[INFO] Build started
[URGENT] Build failed
The first call omits urgent, so the compiler supplies false. The second call uses a named argument to make the boolean value self-explanatory. This is better than PrintStatus("Build failed", true), where the meaning of true is less obvious.
Several Optional Parameters with Named Arguments
using System;
class Program
{
static void Main()
{
CreateTicket("Cannot sign in");
CreateTicket("Payment failed", priority: "High");
CreateTicket("Typo on page", assignedTo: "Maya", sendEmail: false);
}
static void CreateTicket(string title, string priority = "Normal", string assignedTo = "Unassigned", bool sendEmail = true)
{
Console.WriteLine($"Title: {title}");
Console.WriteLine($"Priority: {priority}");
Console.WriteLine($"Assigned to: {assignedTo}");
Console.WriteLine($"Email: {sendEmail}");
Console.WriteLine();
}
}
Output:
Title: Cannot sign in
Priority: Normal
Assigned to: Unassigned
Email: True
Title: Payment failed
Priority: High
Assigned to: Unassigned
Email: True
Title: Typo on page
Priority: Normal
Assigned to: Maya
Email: False
The method has one required parameter and three optional parameters. The third call skips priority while supplying assignedTo and sendEmail. Named arguments are what make that possible and readable.
Using null as a Default for Runtime Values
using System;
using System.Globalization;
class Program
{
static void Main()
{
PrintTimestamp("Saved", new DateTime(2026, 7, 24, 14, 30, 0));
PrintTimestamp("Queued");
}
static void PrintTimestamp(string label, DateTime? when = null)
{
DateTime actualTime = when ?? new DateTime(2026, 7, 24, 9, 0, 0);
Console.WriteLine($"{label}: {actualTime.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture)}");
}
}
Output:
Saved: 2026-07-24 14:30
Queued: 2026-07-24 09:00
You cannot use DateTime.Now as an optional parameter default because it is a runtime value. A common pattern is to use null as the default, then choose the runtime value inside the method body. This example uses a fixed fallback time so the output is predictable.
Optional Parameters and Overload Resolution
using System;
class Program
{
static void Main()
{
Save("notes.txt");
Save("notes.txt", overwrite: true);
}
static void Save(string fileName)
{
Console.WriteLine("basic save: " + fileName);
}
static void Save(string fileName, bool overwrite = false)
{
Console.WriteLine("overwrite save: " + fileName + ", " + overwrite);
}
}
Output:
basic save: notes.txt
overwrite save: notes.txt, True
When both overloads are available, Save("notes.txt") chooses the overload with exactly one parameter. The compiler does not automatically prefer the overload with an optional parameter. This is legal, but it can surprise readers, so avoid overload sets that compete with optional defaults.
How Optional Parameters Work Step by Step
- The compiler reads the method declaration and records each optional parameter’s default value in metadata.
- At a call site, the compiler checks which arguments the caller supplied, including any named arguments.
- For omitted optional arguments, the compiler inserts the declared default value into the call.
- Overload resolution then chooses the best method based on the resulting argument list and normal conversion rules.
- The compiled program calls the selected method with a value for every parameter. The CLR does not run a special optional-parameter lookup for ordinary C# calls.
This explains two important behaviors. First, a method with optional parameters still receives ordinary parameter values when it runs. Second, changing a default value in a compiled library is a source compatibility change, but callers may need recompilation before they observe the new default.
Common Mistakes
Putting Optional Parameters Before Required Parameters
static void CreateUser(string role = "Member", string name)
{
Console.WriteLine(name + ": " + role);
}
This does not compile because a required parameter cannot follow an optional parameter. Put required information first, then defaults:
using System;
class Program
{
static void Main()
{
CreateUser("Ada");
CreateUser("Grace", role: "Admin");
}
static void CreateUser(string name, string role = "Member")
{
Console.WriteLine(name + ": " + role);
}
}
Output:
Ada: Member
Grace: Admin
Using a Runtime Expression as a Default
static void Log(string message, DateTime when = DateTime.Now)
{
Console.WriteLine($"{when}: {message}");
}
This does not compile because DateTime.Now is evaluated at runtime. Optional parameter defaults must be values the compiler can embed. Use null or an overload instead:
using System;
using System.Globalization;
class Program
{
static void Main()
{
Log("Started", new DateTime(2026, 7, 24, 8, 15, 0));
Log("Finished");
}
static void Log(string message, DateTime? when = null)
{
DateTime actualTime = when ?? new DateTime(2026, 7, 24, 8, 45, 0);
Console.WriteLine($"{actualTime.ToString("HH:mm", CultureInfo.InvariantCulture)} - {message}");
}
}
Output:
08:15 - Started
08:45 - Finished
Hiding Too Much Behind Boolean Defaults
using System;
class Program
{
static void Main()
{
Export("orders.csv", true, false);
}
static void Export(string fileName, bool includeHeader = true, bool compress = false)
{
Console.WriteLine($"{fileName}: header={includeHeader}, compress={compress}");
}
}
Output:
orders.csv: header=True, compress=False
This compiles, but the call is hard to read because true and false do not explain themselves. Prefer named arguments, or use a small options type when the settings grow:
using System;
class Program
{
static void Main()
{
Export("orders.csv", includeHeader: true, compress: false);
}
static void Export(string fileName, bool includeHeader = true, bool compress = false)
{
Console.WriteLine($"{fileName}: header={includeHeader}, compress={compress}");
}
}
Output:
orders.csv: header=True, compress=False
Best Practices
- Use optional parameters for values that truly have a stable, obvious default.
- Keep required parameters first and optional parameters last.
- Use named arguments when passing optional booleans, numbers, or several values of the same type.
- Do not use optional parameters to hide important business decisions. If the caller should think about a value, make it required.
- Use
nullplus logic inside the method when the default depends on runtime state. - Be cautious in public libraries. Changing an optional default may not affect already compiled callers until they rebuild.
- Avoid combining many overloads with optional parameters unless each call shape remains obvious.
- When a method grows many optional settings, consider an options class or record instead of a long parameter list.
Practice Exercises
- Write a
FormatNamemethod with requiredfirstNameandlastNameparameters, plus an optionalincludeLastFirstboolean. Use a named argument when calling it withtrue. - Create a
PrintInvoicemethod with a required invoice number and optionalcurrencyandshowPaidStampparameters. Print three calls that use different combinations. - Write a
ScheduleRemindermethod that accepts a required message and an optional nullableDateTime. If the date is omitted, choose a default inside the method body.
Summary
- Optional parameters let callers omit arguments when a method declares default values.
- The compiler inserts omitted defaults at the call site; the method still receives ordinary parameter values.
- Defaults must be compile-time values such as literals,
null,default, or enum values. - Named arguments make optional-parameter calls clearer and let callers skip earlier optional values.
- Optional parameters must follow required parameters.
- For changing public APIs, many confusing settings, or runtime defaults, overloads or options objects may be a better fit.
