TypeScript Default Parameters

When I write TypeScript, I use default parameters all the time to make functions easier to call and less error‑prone. In this guide, I’ll walk through how default parameters work, where they shine, some common pitfalls, and a few patterns I use in real projects.

What are the default parameters in TypeScript?

A default parameter is just a function parameter that has a fallback value.

If the caller:

  • does not pass that argument at all, or
  • passes undefined explicitly

Then TypeScript (and JavaScript) uses the default value you defined.

This is handy when you want a parameter to be optional for the caller, but you still want a safe value at runtime instead of undefined.

Basic syntax and a simple example

Here’s the most basic form:

function greet(name: string, greeting: string = "Hello"): string {
return `${greeting}, ${name}!`;
}

console.log(greet("Alice")); // Hello, Alice!
console.log(greet("Bob", "Hi")); // Hi, Bob!
console.log(greet("Sam", undefined)); // Hello, Sam!

A few things to notice:

  • greeting has a default value “Hello”.
  • When I call greet(“Alice”), the default is used.
  • When I call greet(“Bob”, “Hi”), the passed value overrides the default.
  • Passing undefined also triggers the default.

This behavior (default applies on undefined) is important and we’ll come back to it.

Default parameters with multiple arguments

You can use defaults on any number of parameters, not just the last one.

Realistically, you’ll often see this around functions that wrap APIs or configuration options. For example, a simple pagination helper:

function fetchUsers(
page: number = 1,
pageSize: number = 20,
includeInactive: boolean = false
): void {
console.log(
`Fetching page ${page} with size ${pageSize}, includeInactive=${includeInactive}`
);
}

fetchUsers(); // page 1, size 20, includeInactive=false
fetchUsers(2); // page 2, size 20, includeInactive=false
fetchUsers(3, 50, true); // page 3, size 50, includeInactive=true

You can refer to the screenshot below to see the output.

TypeScript Default Parameters

Why this is nice:

  • Callers get sensible defaults most of the time: fetchUsers() is enough.
  • If a page needs a different page size or needs inactive users, they can override the defaults.

Another example you’ll commonly see in apps:

function createUser(
firstName: string,
lastName: string,
age: number = 30,
country: string = "USA"
): string {
return `Name: ${firstName} ${lastName}, Age: ${age}, Country: ${country}`;
}

console.log(createUser("John", "Doe"));
// Name: John Doe, Age: 30, Country: USA

console.log(createUser("Jane", "Smith", 25));
// Name: Jane Smith, Age: 25, Country: USA

console.log(createUser("Emily", "Johnson", 40, "Canada"));
// Name: Emily Johnson, Age: 40, Country: Canada

You can refer to the screenshot below to see the output.

Default Parameters TypeScript

Defaults keep the function signature readable and avoid a bunch of if checks inside the function body.

Default parameters vs optional parameters (?)

This is a common confusion: default parameters and “optional” parameters are not the same thing.

Optional parameter (?)

With an optional parameter, the caller can omit the argument, but there is no automatic runtime default.

function logMessage(message: string, prefix?: string): void {
console.log(prefix + message);
}

logMessage("System started"); // "undefinedSystem started"

Here:

  • prefix is typed as string | undefined.
  • If you call logMessage(“System started”), prefix is undefined.
  • TypeScript lets this compile, but at runtime you get “undefinedSystem started” unless you add your own checks.

Realistically, you’d write:

function logMessageSafe(message: string, prefix?: string): void {
const actualPrefix = prefix ?? "[INFO] ";
console.log(actualPrefix + message);
}

Default parameter

With a default parameter, you get a real runtime default:

function logMessageDefault(
message: string,
prefix: string = "[INFO] "
): void {
console.log(prefix + message);
}

logMessageDefault("System started"); // [INFO] System started
logMessageDefault("Disk full", "[WARN] "); // [WARN] Disk full

You can refer to the screenshot below to see the output.

Default Parameters in TypeScript

Here:

  • prefix is always a string at runtime.
  • You don’t need extra checks inside the function.

Rule of thumb I use:

  • If undefined is a meaningful value, I use an optional parameter (?).
  • If I always want a usable value, I use a default parameter.

How default parameters behave with undefined and null

One subtle but important detail: the default only kicks in when the argument is undefined or omitted.

function toUpper(text: string = "default"): void {
console.log(text.toUpperCase());
}

toUpper(); // DEFAULT
toUpper(undefined); // DEFAULT
// @ts-expect-error
toUpper(null); // runtime error
  • No argument → uses “default”.
  • undefined → uses “default”.
  • null → no default, and at runtime you’ll get an error because null.toUpperCase() is invalid.

So if there’s a chance null might appear, you still need to handle it explicitly.

Use default parameters with destructured objects

For anything with more than a couple of options, I prefer a single “options” object with defaults in the parameter list. It keeps the call site readable and the function flexible.

Let’s say we have a small HTTP wrapper:

interface RequestConfig {
url: string;
method?: string;
timeout?: number;
}

function fetchData({
url,
method = "GET",
timeout = 5000,
}: RequestConfig): void {
console.log(
`Fetching data from ${url} with method ${method} and timeout ${timeout}`
);
}

fetchData({ url: "https://api.example.com" });
// Fetching data from https://api.example.com with method GET and timeout 5000

fetchData({ url: "https://api.example.com", method: "POST" });
// Fetching data from https://api.example.com with method POST and timeout 5000

fetchData({ url: "https://api.example.com", timeout: 10000 });
// Fetching data from https://api.example.com with method GET and timeout 10000

Why I like this pattern:

  • The call site shows named arguments, which is easier to read.
  • Each property can have its own default (method = “GET”, timeout = 5000).
  • The RequestConfig interface stays simple and optional.

You can also go one level deeper with nested defaults, but I’d keep it readable:

interface RequestOptions {
url: string;
retryCount?: number;
headers?: {
Authorization?: string;
};
}

function request({
url,
retryCount = 3,
headers: { Authorization = "anonymous" } = {},
}: RequestOptions): void {
console.log(url, retryCount, Authorization);
}

request({ url: "https://api.example.com" });
// https://api.example.com 3 anonymous

request({
url: "https://api.example.com",
retryCount: 5,
headers: { Authorization: "Bearer token" },
});
// https://api.example.com 5 Bearer token

This is powerful, but if your parameter list starts to feel like a puzzle, it’s often better to keep the defaults simple and move more complex logic inside the function body.

Real‑world patterns I use default parameters for

Here are some patterns I actually use in projects.

Logging helpers

type LogLevel = "info" | "warn" | "error";

function log(message: string, level: LogLevel = "info"): void {
console.log(`[${level.toUpperCase()}] ${message}`);
}

log("User signed in");
// [INFO] User signed in

log("User not found", "warn");
// [WARN] User not found

This gives you a usable default level while still allowing overrides.

Feature flags

function isFeatureEnabled(
flagName: string,
defaultValue: boolean = false
): boolean {
// In a real app, you’d look this up from config, environment, or remote flags.
console.log(`Checking feature flag: ${flagName}`);
return defaultValue;
}

isFeatureEnabled("new-signup-flow"); // false
isFeatureEnabled("beta-dashboard", true); // true

I use this pattern when integrating with feature flag systems or configuration services.

Simple utility helpers

function padId(id: number, width: number = 5, char: string = "0"): string {
const str = id.toString();
if (str.length >= width) return str;
return char.repeat(width - str.length) + str;
}

padId(42); // "00042"
padId(42, 3); // "042"
padId(42, 4, " "); // " 42"

Callers can usually just call padId(42) and ignore the rest.

Common Issues and Best Practices

Over time, these are the main things I’ve learned to watch for.

1. Don’t overdo parameter defaults

If a function ends up with something like:

function process(
a: number = 1,
b: number = 2,
c: number = 3,
d: number = 4,
e: number = 5
) {
// ...
}

It’s probably doing too much.

When you see more than two or three parameters with defaults, consider switching to an options object:

interface ProcessOptions {
a?: number;
b?: number;
c?: number;
d?: number;
e?: number;
}

function process({
a = 1,
b = 2,
c = 3,
d = 4,
e = 5,
}: ProcessOptions) {
// ...
}

This scales better and is more self‑documenting at the call site.

2. Be careful with null vs undefined

As we saw earlier, defaults apply only on undefined, not null.

If there’s any chance a value comes from JSON or user input that might be null, you still need to guard against that inside the function.

3. Keep default expressions simple

Yes, you can do this:

function getConfig(config: Config = getDefaultConfigFromDisk()) {
// ...
}

But then you’re doing I/O in the parameter list, which can get confusing.

I prefer:

function getConfig(config?: Config) {
const actualConfig = config ?? getDefaultConfigFromDisk();
// ...
}

Short rule: keep defaults lightweight and predictable. Move heavier logic into the body.

4. Consider parameter order

In JavaScript/TypeScript you can have defaults before non‑defaults, but it can lead to awkward calls.

function example(a: number = 1, b: number) {
console.log(a, b);
}

You’d have to explicitly pass undefined to use the default for a and still provide b.

In practice, I keep non‑default parameters first and put defaulted ones after them. If you need more flexibility, again, an options object is usually clearer.

When to use default parameters

I reach for default parameters when:

  • I want the function to be easy to call with minimal arguments.
  • There’s a clear “typical” value for a parameter.
  • I want to avoid undefined checks everywhere in the body.
  • I’m designing helpers, logging utilities, or small config‑style functions.

I usually avoid them when:

  • The number of parameters is large and growing.
  • The default value is expensive to compute.
  • I need to handle complex logic around null vs undefined vs “not passed”.

Try it yourself (small exercise)

If you want a quick exercise, try this:

  • Write a function formatPrice(amount: number, currency: string = “USD”, locale: string = “en-US”) that returns a formatted currency string using Intl.NumberFormat.
  • Call it with just the amount, then with all arguments, and see how the defaults behave.

Here’s one possible solution:

function formatPrice(
amount: number,
currency: string = "USD",
locale: string = "en-US"
): string {
return new Intl.NumberFormat(locale, {
style: "currency",
currency,
}).format(amount);
}

console.log(formatPrice(19.99)); // $19.99 (in US locale)
console.log(formatPrice(19.99, "EUR", "de-DE")); // 19,99 €

This is a nice example that feels realistic if you’re working on a web app for users in the United States.

FAQ: common questions about TypeScript default parameters

Can I use default parameters in arrow functions?

Yes. Arrow functions support default parameters just like regular functions.
tsconst multiply = (a: number, b: number = 2): number => a * b;

multiply(5); // 10
multiply(5, 3); // 15

Can I use default parameters in class methods and constructors?

Yes. You can use them in both methods and constructors.
tsclass User {
constructor(
public name: string,
public role: string = “user”
) {}

greet(greeting: string = “Hello”): string {
return `${greeting}, ${this.name}!`;
}
}

const user = new User(“David”);
console.log(user.role); // “user”
console.log(user.greet()); // “Hello, David!”

Do default parameters affect TypeScript types?

TypeScript infers the parameter type from the annotation and the default together. If you write:
tsfunction demo(count = 10) {
// …
}

How are default parameters different from function overloads?

Function overloads are a type‑level feature in TypeScript to describe different call signatures. Default parameters are a runtime feature from JavaScript to provide actual values.
You can use them together, but they solve different problems. Defaults give you fallback values at runtime; overloads give you better type checking for multiple ways to call the function.

Summary

Default parameters are one of those simple features that quietly make TypeScript code much easier to work with. They:

  • Give your functions sensible, documented defaults.
  • Reduce if (!value) checks and undefined handling.
  • Make your APIs easier to call and harder to misuse.

If you get into the habit of using them thoughtfully, especially in small helpers, option objects, and class methods, you’ll notice your code getting cleaner and more predictable.

You may 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.