When I write TypeScript functions, I often want some arguments to be optional without creating multiple overloads or duplicating logic. Optional parameters are exactly for that. They let me keep function signatures flexible, while TypeScript still checks types for me.
In this tutorial, I’ll walk through how optional parameters in TypeScript work, the rules you need to know, and the trade‑offs with default values and undefined. I’ll also share patterns I actually use in real projects and where I switch to an “options” object instead.
What are optional parameters in TypeScript?
By default, TypeScript treats every parameter as required: if you don’t pass it, you get an error at compile time. Optional parameters are parameters that you’re allowed to omit when calling a function.
You mark a parameter as optional by adding a ? after its name in the function definition:
function greet(firstName: string, lastName?: string): string {
if (lastName) {
return `Hello, ${firstName} ${lastName}!`;
}
return `Hello, ${firstName}!`;
}
console.log(greet("John")); // "Hello, John!"
console.log(greet("John", "Doe")); // "Hello, John Doe!"I executed the above example code and added the screenshot below.

If you don’t pass an optional parameter, its value inside the function is undefined, so you should handle that case explicitly.
Why use optional parameters?
I reach for optional parameters when a function has a “core” required argument and one or two “nice to have” extras.
They’re handy when:
- You want a simple API with a sensible minimal call.
- Extra arguments refine behavior but aren’t always needed.
- You’d otherwise create several overloads just to handle “with X” / “without X”.
I avoid them when a function already has many optional flags, because then a single options object usually reads better.
The rules: parameter order and behavior
There are a couple of important rules that the TypeScript compiler enforces around optional parameters.
Rule 1: Optional parameters must come last
All required parameters must come before any optional ones. This will not compile:
// ❌ This is invalid – optional before required
function logMessage(prefix?: string, message: string) {
console.log(prefix, message);
}
You need to write it like this:
// ✅ Required first, optional last
function logMessage(message: string, prefix?: string) {
const label = prefix ?? "INFO";
console.log(`${label}: ${message}`);
}
This rule exists because TypeScript needs a clear way to match arguments to parameters when some are missing.
Rule 2: Optional parameters are undefined when omitted
If you don’t pass an optional parameter, its value is undefined inside the function:
function example(value?: number) {
if (value === undefined) {
console.log("No value provided");
return;
}
console.log("Value:", value);
}Using a strict check like value === undefined or nullish coalescing (value ?? defaultValue) is clearer than relying on truthiness.
Optional parameters vs default parameters
Optional parameters and default parameters are related but not the same thing.
Both let you omit an argument, but they behave a bit differently:
- Optional: parameter may be omitted, value is
undefinedif omitted. - Default: parameter may be omitted, but if you omit it (or explicitly pass
undefined), TypeScript uses the default expression.
Here’s a side‑by‑side example.
Optional parameter
function formatCurrency(amount: number, currencyCode?: string): string {
const code = currencyCode ?? "USD";
return `${code} ${amount.toFixed(2)}`;
}
formatCurrency(19.99); // "USD 19.99"
formatCurrency(19.99, "EUR"); // "EUR 19.99"
formatCurrency(19.99, undefined); // "USD 19.99"Default parameter
function formatCurrencyWithDefault(
amount: number,
currencyCode: string = "USD"
): string {
return `${currencyCode} ${amount.toFixed(2)}`;
}
formatCurrencyWithDefault(19.99); // "USD 19.99"
formatCurrencyWithDefault(19.99, "EUR"); // "EUR 19.99"
formatCurrencyWithDefault(19.99, undefined); // "USD 19.99"
I use default parameters when I always want a fallback and rarely need to distinguish “not passed” from “passed as undefined”.
Optional vs “required but can be undefined”
This is a subtle but important difference that trips a lot of people.
Compare these two signatures:
// A: optional parameter
function doSomethingA(flag?: boolean) {
// flag is boolean | undefined, but the parameter is optional
}
// B: required parameter that may be undefined
function doSomethingB(flag: boolean | undefined) {
// caller must pass something, even if it's explicitly undefined
}
- In version A, the caller can omit
flagentirely. - In version B, the caller must pass an argument, and
doSomethingB()is a compile‑time error.
I think of it like this:
flag?: boolean→ “You don’t have to pass this.”flag: boolean | undefined→ “You must pass something, but it may be undefined.”
When I care about “argument omitted vs explicitly set to undefined,” I lean toward default parameters or explicit unions instead of overloading optional parameters.
Simple example: greeting with optional last name
Let’s start with a clean, minimal example using a U.S. style full name:
function getFullName(firstName: string, lastName?: string): string {
if (lastName) {
return `${firstName} ${lastName}`;
}
return firstName;
}
// Usage
console.log(getFullName("Emily")); // "Emily"
console.log(getFullName("Emily", "Johnson")); // "Emily Johnson"Here, lastName is optional because not every context needs it, but firstName is always required.
Practical example: user registration
This is close to what I’d actually write in a small app: email is required, phone number is optional.
function registerUser(
username: string,
email: string,
phoneNumber?: string
): string {
if (phoneNumber) {
return `User ${username} registered with email ${email} and phone ${phoneNumber}.`;
}
return `User ${username} registered with email ${email}.`;
}
console.log(registerUser("michael_smith", "michael@example.com"));
// User michael_smith registered with email michael@example.com.
console.log(
registerUser("sarah_jones", "sarah@example.com", "404-555-1212")
);
// User sarah_jones registered with email sarah@example.com and phone 404-555-1212.I executed the above example code and added the screenshot below.

Notice again: required parameters (username, email) come first, optional (phoneNumber) comes last.
Scheduling example: appointment time as optional
Here’s a simple scheduling function where time is optional, but date is not.
function bookAppointment(
clientName: string,
date: string,
time?: string
): string {
if (time) {
return `Appointment for ${clientName} is scheduled on ${date} at ${time}.`;
}
return `Appointment for ${clientName} is scheduled on ${date}.`;
}
console.log(bookAppointment("Olivia Davis", "2026-06-15"));
// Appointment for Olivia Davis is scheduled on 2026-06-15.
console.log(bookAppointment("Liam Miller", "2026-06-15", "2:00 PM"));
// Appointment for Liam Miller is scheduled on 2026-06-15 at 2:00 PM.
This pattern shows up a lot: the “when” part often has optional details like time, timezone, or location.
Notifications example: optional message body
Another common pattern is a notification service where you always need a recipient and subject, but the body might be optional.
function sendNotification(
recipientEmail: string,
subject: string,
messageBody?: string
): string {
if (messageBody) {
return `Notification sent to ${recipientEmail}: ${subject} — ${messageBody}`;
}
return `Notification sent to ${recipientEmail}: ${subject}`;
}
console.log(sendNotification("alex@example.com", "System Update"));
// Notification sent to alex@example.com: System Update
console.log(
sendNotification(
"alex@example.com",
"System Update",
"We’ll restart the server at 9:00 PM EST."
)
);
// Notification sent to alex@example.com: System Update — We’ll restart the server at 9:00 PM EST.
The nice part is that callers can use the shortest version that works for them.
When optional parameters start to hurt
Optional parameters are great for a one‑off extra argument or two, but they break down when you have several optional bits of behavior.
Imagine this:
function createReport(
name: string,
includeCharts?: boolean,
includeSummary?: boolean,
includeRawData?: boolean
) {
// ...
}
Now calls like createReport(“Sales Q1”, true, false, true) are hard to read. You have to remember the exact order of those booleans.
At that point, I usually switch to an options object.
Use an options object instead
An options object keeps the call site readable and handles many optional bits more gracefully.
interface ReportOptions {
includeCharts?: boolean;
includeSummary?: boolean;
includeRawData?: boolean;
}
function createReport(name: string, options: ReportOptions = {}) {
const {
includeCharts = true,
includeSummary = true,
includeRawData = false,
} = options;
// generate the report using these choices
console.log(
`Creating report "${name}" with charts=${includeCharts}, summary=${includeSummary}, rawData=${includeRawData}`
);
}
// Usage
createReport("Sales Q1");
createReport("Sales Q1", { includeRawData: true });
createReport("Sales Q1", { includeCharts: false, includeSummary: false });I executed the above example code and added the screenshot below.

This gives me:
- Named options at the call site.
- Defaults in one place.
- The ability to add new optional behavior later without breaking existing calls.
How to skip an optional parameter in the middle
Sometimes you see a function signature like this:
function error(
message: string,
title?: string,
autoHideAfter?: number
) {
// ...
}
If you want to skip title but still pass autoHideAfter, you can pass undefined in that position:
error("Something went wrong", undefined, 3000);It works, but I don’t love the ergonomics. It’s easy to forget which argument you’re skipping. In those cases, I favor refactoring to an options object:
interface ErrorOptions {
title?: string;
autoHideAfter?: number;
}
function error(message: string, options?: ErrorOptions) {
const title = options?.title ?? "Error";
const timeout = options?.autoHideAfter ?? 2000;
console.log(`[${title}] ${message} (autoHideAfter: ${timeout} ms)`);
}
error("Something went wrong", { autoHideAfter: 3000 });That’s more self‑documenting at the call site and scales better over time.
Common mistakes with optional parameters
Here are the issues I often see in real code reviews.
1. Optional before required
We already covered this, but it’s the big one: you can’t put an optional parameter before a required one. The compiler will complain
2. Use optional parameters where default values are clearer
If you always fall back to the same value, it’s often simpler to use a default parameter:
// Less clear
function paginate(page?: number, pageSize?: number) {
const currentPage = page ?? 1;
const size = pageSize ?? 20;
// ...
}
// Clearer
function paginate2(page: number = 1, pageSize: number = 20) {
// ...
}
The second version clearly shows the defaults right in the signature and works well for most cases.
3. Confusing “omitted” with “falsy”
Checking if (value) is fine for many cases, but it treats 0, "", and false as “not provided,” which may not be what you want.
If you only care about “argument omitted,” prefer:
if (value === undefined) {
// argument was omitted
}or:
const normalized = value ?? someDefault;
Nullish coalescing (??) only uses the default for null or undefined.
How I decide between ?, default values, and options objects
Here’s how I approach this in day‑to‑day TypeScript code.
I use ? (optional parameter) when:
- There’s only one optional argument, or maybe two.
- I don’t need complex defaults.
- The function is small and local.
Example: formatCurrency(amount: number, currencyCode?: string).
I use default parameters when:
- I always want a clear fallback value.
- The fallback is a simple expression.
- I rarely need to distinguish “omitted” from “undefined.”
Example: paginate(page: number = 1, pageSize: number = 20).
I use an options object when:
- There are several optional behaviors.
- Call sites look confusing with many positional arguments.
- I expect new options to be added later.
Example: createReport(name: string, options?: ReportOptions).
Having these three tools and using them deliberately makes APIs easier for teammates to read and use.
Quick checklist for optional parameters
When I’m writing or reviewing code, I mentally run through this checklist:
- Are all required parameters before any optional ones?
- Am I handling undefined explicitly when needed?
- Would a default parameter be simpler here?
- Is it time to refactor to an options object?
- Am I overusing optional parameters where a different design would be clearer?
If I can answer those questions confidently, the function signature is usually in a good place.
Key takeaways
- Use param?: Type to make a parameter optional; callers can omit it, and its value will be undefined when omitted.
- Keep all required parameters before any optional parameters, or the compiler will flag it.
- Default parameters (param: Type = value) also make arguments optional and are often better when you always want a fallback.
- Don’t confuse param?: T (optional) with param: T | undefined (required but possibly undefined).
- Consider using objects when you have several optional flags or when call sites are hard to read.
You may also like to read:
- How to Get Index in forEach Loop in TypeScript?
- for…in Loops in TypeScript
- How to Use for…of Loops in TypeScript?
- TypeScript forEach Loop with Index

Bijay Kumar is an experienced Python and AI professional who enjoys helping developers learn modern technologies through practical tutorials and examples. His expertise includes Python development, Machine Learning, Artificial Intelligence, automation, and data analysis using libraries like Pandas, NumPy, TensorFlow, Matplotlib, SciPy, and Scikit-Learn. At PythonGuides.com, he shares in-depth guides designed for both beginners and experienced developers. More about us.