Question

How to Handle Optional Parameters in TypeScript When Skipping Earlier Arguments

Question

Given the following TypeScript interface:

export interface INotificationService {
    error(message: string, title?: string, autoHideAfter?: number): void;
}

How can error() be called so that title is not provided, but autoHideAfter is set to 1000?

Short Answer

By the end of this page, you will understand how optional parameters work in TypeScript, why you cannot skip a middle positional argument directly, and the common ways to solve this problem, especially by passing undefined or by redesigning the function to use an options object.

Concept

In TypeScript, function parameters are usually positional. That means arguments are matched by their place in the parameter list:

error(message, title, autoHideAfter)

So even if title is optional, the third argument still belongs to the third position. You cannot jump over the second parameter unless you explicitly put something in that slot.

For this function:

error(message: string, title?: string, autoHideAfter?: number): void;

TypeScript reads the arguments like this:

  • first argument → message
  • second argument → title
  • third argument → autoHideAfter

If you want to provide autoHideAfter but not title, you need to keep the second position empty in a valid way. The normal solution is:

error("Something went wrong", undefined, 1000);

This works because undefined tells TypeScript and JavaScript that the optional title was intentionally not supplied.

This matters in real programming because APIs with many optional positional parameters quickly become hard to read and easy to misuse. That is why many real codebases prefer an options object instead.

Mental Model

Think of function parameters like labeled seats in a row:

  • Seat 1 = message
  • Seat 2 = title
  • Seat 3 = autoHideAfter

If you want to put someone in Seat 3, you still have to acknowledge Seat 2. You cannot magically skip over it. Using undefined is like saying:

“Seat 2 is intentionally empty, and Seat 3 should get this value.”

An options object is like replacing numbered seats with name tags, so you can fill only the fields you want.

Take Quiz

Syntax and Examples

The direct call for this signature is:

error("Something went wrong", undefined, 1000);

Example with the interface

interface INotificationService {
  error(message: string, title?: string, autoHideAfter?: number): void;
}

const notificationService: INotificationService = {
  error(message, title, autoHideAfter) {
    console.log({ message, title, autoHideAfter });
  }
};

notificationService.error("Save failed", undefined, 1000);

Output:

{
  message: "Save failed",
  title: undefined,
  autoHideAfter: 1000
}

Why this is needed

This does not work as intended:

notificationService.error("Save failed", 1000);

TypeScript treats 1000 as the second argument, which belongs to title. Since title should be a string, this is a type error.

Better API design with an options object

If you control the API, a cleaner design is:

interface NotificationOptions {
  title?: string;
  autoHideAfter?: number;
}

interface INotificationService {
  error(message: string, options?: NotificationOptions): void;
}

const notificationService: INotificationService = {
  error(message, options) {
    console.log({ message, options });
  }
};

notificationService.error("Save failed", { autoHideAfter: 1000 });

This is easier to read and avoids argument-order confusion.

Step by Step Execution

Consider this call:

notificationService.error("Network error", undefined, 1000);

Here is what happens step by step:

  1. "Network error" is passed into message.
  2. undefined is passed into title.
  3. 1000 is passed into autoHideAfter.

So inside the function, the values are:

message = "Network error"
title = undefined
autoHideAfter = 1000

Traceable example

function error(message: string, title?: string, autoHideAfter?: number): void {
  console.log("message:", message);
  console.log("title:", title);
  console.log("autoHideAfter:", autoHideAfter);
}

error("Network error", undefined, 1000);

Console output:

message: Network error
title: undefined
autoHideAfter: 1000

What if you pass only two arguments?

error("Network error", "Warning");

Now the mapping becomes:

  • message = "Network error"
  • title = "Warning"
  • autoHideAfter = undefined

Again, TypeScript always matches by position.

Real World Use Cases

Optional parameters appear often in real applications:

  • Notifications and toasts
    • message text is required
    • title, timeout, or severity may be optional
  • Logging utilities
    • required log message
    • optional tag, metadata, or timestamp override
  • HTTP helper functions
    • required URL
    • optional headers, timeout, retries
  • UI dialog helpers
    • required content
    • optional title, size, closable flag
  • Data formatting functions
    • required value
    • optional locale, precision, currency symbol

In all these cases, too many optional positional parameters can make calls unclear. That is why object parameters are common in production code.

Take Quiz

Real Codebase Usage

In real codebases, developers usually handle this in one of two ways.

1. Pass undefined for skipped optional positional arguments

This is common when:

  • the API already exists
  • you cannot change the function signature
  • there are only a few parameters

Example:

notificationService.error("Login failed", undefined, 3000);

2. Use an options object

This is preferred when:

  • there are multiple optional settings
  • readability matters
  • the API may grow over time

Example:

notificationService.error("Login failed", {
  autoHideAfter: 3000
});

Common patterns in real projects

Validation

function error(message: string, title?: string, autoHideAfter?: number) {
  if (!message) {
    throw new Error("Message is required");
  }
}

Default values

function error(message: string, title?: string, autoHideAfter: number = 5000) {
  console.log(message, title, autoHideAfter);
}

Guard clauses

function error(message: string, title?: string, autoHideAfter?: number) {
  if (autoHideAfter !== undefined && autoHideAfter < 0) {
    return;
  }

  console.log(message, title, autoHideAfter);
}

Config-style API

type NotificationConfig = {
  title?: string;
  autoHideAfter?: number;
};

function error(message: string, config: NotificationConfig = {}) {
  const { title, autoHideAfter = 5000 } = config;
  console.log(message, title, autoHideAfter);
}

Common Mistakes

1. Trying to skip a middle argument without undefined

Broken code:

error("Problem", 1000);

Why it fails:

  • 1000 is passed as title
  • but title should be a string

Fix:

error("Problem", undefined, 1000);

2. Assuming optional means named

Some beginners think this is allowed:

error("Problem", autoHideAfter: 1000);

This is not valid TypeScript function call syntax. Function arguments are positional unless the function accepts an object.

3. Using null without checking API expectations

This may compile depending on your settings, but it means something different from undefined in many codebases:

error("Problem", null, 1000);

Prefer undefined when you mean “not provided,” unless the API specifically uses null.

4. Designing functions with too many optional positional parameters

Hard-to-read API:

function error(message: string, title?: string, autoHideAfter?: number, sticky?: boolean, theme?: string) {}

This becomes confusing quickly.

Better:

type ErrorOptions = {
  title?: string;
  autoHideAfter?: number;
  sticky?: boolean;
  theme?: string;
};

function error(message: string, options?: ErrorOptions) {}

5. Forgetting readability

Even if this is valid:

error("Problem", undefined, 1000);

it may be less clear than an object-based API when many settings are involved.

Comparisons

ApproachExampleProsCons
Optional positional parameterserror("Failed", undefined, 1000)Simple for small APIsHard to read when skipping values
Options objecterror("Failed", { autoHideAfter: 1000 })Clear, flexible, scalableSlightly more verbose
Overloaded signaturesMultiple function definitionsCan improve type safety for common call shapesMore complex to maintain

Positional parameters vs options object

Positional

error("Failed", "Warning", 1000);

Best when:

  • there are very few parameters
  • order is obvious
  • most parameters are usually provided

Options object

error("Failed", { title: "Warning", autoHideAfter: 1000 });

Best when:

  • many settings are optional
  • you want self-documenting calls
  • the API may expand later

undefined vs null

ValueTypical meaning
undefinedNot provided / missing
nullIntentionally empty value
In optional parameter calls, undefined is usually the correct choice when skipping an argument.
Take Quiz

Cheat Sheet

function error(message: string, title?: string, autoHideAfter?: number): void {}

To skip title and set autoHideAfter

error("Message", undefined, 1000);

Key rules

  • TypeScript function arguments are positional.
  • Optional parameters can be omitted only from the end naturally.
  • To skip an earlier optional argument and provide a later one, pass undefined.
  • If the API has many optional values, prefer an options object.

Common pattern

type Options = {
  title?: string;
  autoHideAfter?: number;
};

function error(message: string, options?: Options): void {}

Good examples

error("Failed");
error("Failed", "Warning");
error("Failed", undefined, 1000);

Bad example

error("Failed", 1000); // 1000 is treated as title

Best redesign for readability

error("Failed", { autoHideAfter: 1000 });

FAQ

Can I skip an optional parameter in TypeScript without passing anything?

Only if it is at the end of the argument list. You cannot skip a middle positional parameter unless you pass undefined.

How do I pass the third argument without the second in TypeScript?

Pass undefined for the second argument:

error("Message", undefined, 1000);

Why does error("Message", 1000) fail?

Because TypeScript treats 1000 as the second argument, which maps to title, and title is expected to be a string.

Is null the same as undefined for optional parameters?

Not exactly. undefined usually means “not provided,” while null often means “intentionally empty.” Use undefined when skipping an optional argument.

What is the best way to handle many optional parameters?

Use an options object. It makes function calls clearer and easier to maintain.

Should I redesign this API if I control it?

Yes, if the function may grow or already has multiple optional parameters. An options object is usually the cleaner choice.

Take Quiz

Mini Project

Description

Build a small notification helper that shows how optional positional parameters behave and how an options-object redesign can make function calls clearer. This project demonstrates both the direct undefined approach and the more maintainable object-based approach.

Goal

Create a notification service that can display an error message, optionally include a title, and optionally set an auto-hide timeout.

Requirements

  1. Create a function that accepts message, optional title, and optional autoHideAfter.
  2. Call the function once with only a message and once with autoHideAfter while skipping title.
  3. Create a second version that uses an options object instead of multiple optional positional parameters.
  4. Log the received values so the parameter mapping is easy to see.
Take Quiz

Keep learning

Related questions

View more