TypeScript Optional Parameters: A Practical Guide

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.

TypeScript Optional Parameters

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 undefined if 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 flag entirely.
  • 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.

Optional Parameters in TypeScript

Notice again: required parameters (usernameemail) 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.

TypeScript Optional Parameter

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:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.