Question

How to Set Default Values for Object Parameters in TypeScript

typescriptarguments

Question

How can I set a default value for a property on an object parameter in TypeScript?

For example, given this function:

function sayName(params: { firstName: string; lastName?: string }) {
  params.lastName = params.lastName || 'smith';
  const name = params.firstName + params.lastName;
  alert(name);
}

sayName({ firstName: 'bob' });

Is there a better alternative to assigning the default inside the function body with:

params.lastName = params.lastName || 'smith';

I was hoping something like this might work directly in the type annotation:

function sayName(params: { firstName: string; lastName: string = 'smith' }) {

I know that for normal function parameters, TypeScript supports defaults like this:

function sayName(firstName: string, lastName = 'smith') {
  const name = firstName + lastName;
  alert(name);
}

sayName('bob');

So what is the correct TypeScript way to provide default values for properties inside an object argument?

Short Answer

By the end of this page, you will understand how default values work for object parameters in TypeScript, why defaults cannot be written inside a type definition, and the best patterns to use instead. You will also learn when to use destructuring defaults, nullish coalescing, and fallback assignment safely.

Concept

In TypeScript, types describe shape, but they do not create runtime values.

That is the key idea behind this question.

When you write a type like this:

{ firstName: string; lastName?: string }

TypeScript is only saying:

  • firstName must exist and be a string
  • lastName is optional
  • if lastName exists, it must be a string

It is not saying what value lastName should get if it is missing.

A default value such as 'smith' is a runtime behavior, not a type rule. That is why this does not work:

function sayName(params: { firstName: string; lastName: string = 'smith' }) {

Type annotations cannot contain executable default assignments.

Why this matters

In real programs, object parameters are common for:

  • configuration objects
  • API options
  • UI component props
  • filtering and sorting options
  • utility function settings

Very often, some properties are optional and should fall back to sensible defaults. TypeScript helps you describe which properties are optional, but you still need JavaScript logic to actually apply those defaults.

Common ways to set defaults

There are three useful patterns:

  1. Destructuring with defaults
  2. Nullish coalescing (??)
  3. Merging with a defaults object

For a single optional property, destructuring is usually the cleanest approach.

Example:

function sayName({ firstName, lastName = 'smith' }: { firstName: string; lastName?: string }) {
  const name = firstName + lastName;
  alert(name);
}

This works because the default is attached to the parameter destructuring, which runs at runtime.

Important detail: || vs ??

Your original code uses:

params.lastName = params.lastName || 'smith';

This works in many cases, but it treats all falsy values as missing, including:

  • '' (empty string)
  • 0
  • false

For a string property, that means an empty string would also become 'smith', which may be wrong.

A safer modern alternative is:

const lastName = params.lastName ?? 'smith';

?? only falls back when the value is null or undefined, which is usually what you want for optional properties.

Mental Model

Think of a TypeScript type as a form template and a default value as a person filling in a blank space.

  • The type says which blanks exist and what kind of values are allowed.
  • The default value is the action of filling in a blank if the user leaves it empty.

So:

  • Type: "There may be a lastName field. If it exists, it must be a string."
  • Default: "If no lastName was provided, use 'smith'."

The template itself does not fill in the value. Your runtime code does.

Another way to think about it:

  • TypeScript types are like blueprints
  • Defaults are like construction decisions made while building

The blueprint does not install the door. The builder does.

Take Quiz

Syntax and Examples

Basic object parameter with an optional property

function sayName(params: { firstName: string; lastName?: string }) {
  const lastName = params.lastName ?? 'smith';
  const name = firstName + lastName;
}

The problem above is that firstName is not defined as a local variable. A corrected version is:

function sayName(params: { firstName: string; lastName?: string }) {
  const lastName = params.lastName ?? 'smith';
  const name = params.firstName + lastName;
  alert(name);
}

Preferred approach: destructuring with a default

function sayName(
  { firstName, lastName = 'smith' }: { firstName: string; lastName?: string }
) {
  const name = firstName + lastName;
  alert(name);
}

sayName({ firstName: 'bob' });
sayName({ firstName: 'bob', lastName: 'lee' });

Why this is good

  • It is short and readable
  • The default is applied immediately
  • You get local variables firstName and lastName
  • It avoids mutating the original object

Using a type alias

This is often easier to read:

type SayNameParams = {
  firstName: string;
  lastName?: string;
};

function sayName({ firstName, lastName = 'smith' }: SayNameParams) {
  const name = firstName + lastName;
  alert(name);
}

Defaulting the whole object too

If the entire argument might be omitted, you can default the object as well:

type SayNameParams = {
  firstName?: string;
  lastName?: string;
};

function sayName({ firstName = 'bob', lastName = 'smith' }: SayNameParams = {}) {
  alert(firstName + lastName);
}

sayName();
sayName({ firstName: 'alice' });

Avoid this when possible

function sayName(params: { firstName: string; lastName?: string }) {
  params.lastName = params.lastName || 'smith';
  alert(params.firstName + params.lastName);
}

This mutates the input object and uses ||, which may replace valid falsy values unintentionally.

Step by Step Execution

Consider this version:

type SayNameParams = {
  firstName: string;
  lastName?: string;
};

function sayName({ firstName, lastName = 'smith' }: SayNameParams) {
  const fullName = firstName + ' ' + lastName;
  console.log(fullName);
}

sayName({ firstName: 'bob' });

What happens step by step

1. The function is called

sayName({ firstName: 'bob' });

The argument object contains:

{ firstName: 'bob' }

2. Parameter destructuring runs

This part of the function executes:

{ firstName, lastName = 'smith' }

TypeScript/JavaScript extracts properties from the object:

  • firstName becomes 'bob'
  • lastName is missing, so the default 'smith' is used

Now the local variables are:

firstName = 'bob'
lastName = 'smith'

3. The full name is built

const fullName = firstName + ' ' + lastName;

That becomes:

const fullName = 'bob smith';

4. The value is printed

console.log(fullName);

Output:

bob smith

If a last name is provided

sayName({ firstName: 'bob', lastName: 'lee' });

Then destructuring produces:

  • firstName = 'bob'
  • lastName = 'lee'

The default is ignored because the property already exists.

Real World Use Cases

Object parameter defaults are very common in real software.

1. Function options objects

type RequestOptions = {
  method?: string;
  timeout?: number;
};

function makeRequest(url: string, { method = 'GET', timeout = 5000 }: RequestOptions = {}) {
  console.log(url, method, timeout);
}

Used in:

  • HTTP helpers
  • database query functions
  • file operations

2. UI component props

type ButtonProps = {
  label: string;
  color?: string;
};

function renderButton({ label, color = 'blue' }: ButtonProps) {
  console.log(`Button: ${label}, color: ${color}`);
}

Used in:

  • React props
  • design systems
  • reusable UI components

3. Search and filtering functions

type SearchOptions = {
  page?: number;
  pageSize?: number;
};

function searchUsers({ page = 1, pageSize = 20 }: SearchOptions = {}) {
  console.log(page, pageSize);
}

Used in:

  • pagination
  • sorting
  • filtering APIs

4. Logging and formatting utilities

type FormatOptions = {
  uppercase?: boolean;
  prefix?: string;
};

function formatMessage(message: string, { uppercase = false, prefix = '' }: FormatOptions = {}) {
  const text = uppercase ? message.toUpperCase() : message;
  return prefix + text;
}

Used in:

  • utility libraries
  • CLI tools
  • data processing scripts
Take Quiz

Real Codebase Usage

In real codebases, developers usually avoid manually mutating incoming parameter objects unless there is a strong reason.

Common pattern: destructure at the function boundary

type Options = {
  retry?: number;
  verbose?: boolean;
};

function runTask({ retry = 3, verbose = false }: Options = {}) {
  if (verbose) {
    console.log(`Retry count: ${retry}`);
  }
}

This is popular because:

  • defaults are visible immediately
  • local variables are easy to use
  • the original object stays unchanged

Pattern: merge defaults with user options

Useful when many options exist.

type Config = {
  host?: string;
  port?: number;
  secure?: boolean;
};

function connect(config: Config) {
  const finalConfig = {
    host: 'localhost',
    port: 3000,
    secure: false,
    ...config,
  };

  console.log(finalConfig);
}

This pattern is common in:

  • library configuration
  • app settings
  • SDK initialization

Pattern: guard clauses with fallback values

function printUserName(user?: { firstName?: string; lastName?: string }) {
  if (!user) {
    console.log('Unknown user');
    return;
  }

  const firstName = user.firstName ?? 'Anonymous';
  const lastName = user.lastName ?? 'User';

  console.log(firstName + ' ' + lastName);
}

This combines:

  • validation
  • early return
  • safe defaulting

Pattern: ?? for preserving valid falsy values

function setVolume({ level }: { level?: number }) {
  const safeLevel = level ?? 50;
  console.log(safeLevel);
}

If level is 0, it stays 0. With ||, it would incorrectly become 50.

Common Mistakes

1. Trying to put a default inside a type annotation

This is invalid:

function sayName(params: { firstName: string; lastName: string = 'smith' }) {
}

Why it fails:

  • type annotations describe types only
  • they cannot run code or assign values

2. Using || when ?? is safer

Broken logic:

function greet({ lastName }: { lastName?: string }) {
  const value = lastName || 'smith';
  console.log(value);
}

If lastName is '', the result becomes 'smith'.

Safer:

function greet({ lastName }: { lastName?: string }) {
  const value = lastName ?? 'smith';
  console.log(value);
}

3. Mutating the incoming object unnecessarily

Less ideal:

function sayName(params: { firstName: string; lastName?: string }) {
  params.lastName = params.lastName ?? 'smith';
}

Why to avoid it:

  • changes the caller's object
  • can create side effects
  • makes debugging harder

Prefer:

function sayName({ firstName, lastName = 'smith' }: { firstName: string; lastName?: string }) {
  console.log(firstName, lastName);
}

4. Forgetting to make the property optional

Broken example:

function sayName({ firstName, lastName = 'smith' }: { firstName: string; lastName: string }) {
  console.log(firstName, lastName);
}

sayName({ firstName: 'bob' });

This causes a type error because lastName is required in the type.

Fix:

function sayName({ firstName, lastName = 'smith' }: { firstName: string; lastName?: string }) {
  console.log(firstName, lastName);
}

5. Forgetting to default the whole object when the parameter itself may be missing

Broken example:

function sayName({ firstName = 'bob' }: { firstName?: string }) {
  console.log(firstName);
}

sayName();

This fails because the function tries to destructure undefined.

Fix:

function sayName({ firstName = 'bob' }: { firstName?: string } = {}) {
  console.log(firstName);
}

Comparisons

ApproachExampleBest forNotes
Destructuring defaultfunction f({ name = 'x' }: T)Clean parameter handlingUsually the best choice
Nullish coalescingconst name = obj.name ?? 'x'Defaults inside function bodyPreserves valid falsy values
Logical OR`const name = obj.name'x'`
Object spread defaultsconst final = { ...defaults, ...options }Many option fieldsGreat for config objects

|| vs ??

OperatorFalls back when value is undefinedFalls back when value is nullFalls back when value is ''Falls back when value is 0
``YesYes
??YesYesNoNo

Destructuring default vs object spread

PatternExampleUse when
Destructuringfunction f({ a = 1 }: T)You only need a few fields directly in the function
Object spreadconst final = { a: 1, ...options }You want a complete normalized config object
Take Quiz

Cheat Sheet

Defaulting object properties in TypeScript

Type with optional property

type Params = {
  firstName: string;
  lastName?: string;
};

Best pattern: destructuring default

function sayName({ firstName, lastName = 'smith' }: Params) {
  console.log(firstName, lastName);
}

Alternative: ?? inside function

function sayName(params: Params) {
  const lastName = params.lastName ?? 'smith';
  console.log(params.firstName, lastName);
}

Default the whole object too

function sayName({ firstName = 'bob', lastName = 'smith' }: Partial<Params> = {}) {
  console.log(firstName, lastName);
}

Do not do this in a type

// Invalid
{ lastName: string = 'smith' }

Use ?? instead of || when missing means only null or undefined

const value = input ?? 'default';

Quick rules

  • Types describe allowed shapes
  • Defaults happen at runtime
  • Mark properties optional with ? if callers may omit them
  • Prefer destructuring for clean parameter defaults
  • Prefer ?? over || when empty strings or 0 should be preserved

FAQ

Can I set a default value directly in a TypeScript type?

No. Types only describe allowed shapes. They do not assign runtime values.

What is the best way to default an optional object property in TypeScript?

Usually, destructuring with a default is the cleanest option:

function f({ name = 'Anonymous' }: { name?: string }) {}

Should I use || or ?? for fallback values?

Use ?? when you only want the fallback for null or undefined. Use || only when all falsy values should trigger the fallback.

Why must lastName be optional if I give it a default?

Because callers may omit it. The type must reflect that by using lastName?: string.

Can I default the whole parameter object too?

Yes:

function f({ name = 'Anonymous' }: { name?: string } = {}) {}

Is mutating the input object a bad idea?

Usually yes. It can cause side effects and make code harder to reason about. Prefer creating local variables or normalized objects.

When should I use object spread for defaults?

Use object spread when you have many options and want one final config object:

const finalOptions = { timeout: 5000, ...options };
Take Quiz

Mini Project

Description

Build a small TypeScript utility that formats a user profile from an options object. This project demonstrates how to accept an object parameter, mark some properties as optional, and apply default values safely without mutating the original input.

Goal

Create a function that prints a formatted user profile using sensible defaults for missing optional properties.

Requirements

  • Define a type for the user profile options object.
  • Require a firstName property.
  • Make lastName, role, and isActive optional.
  • Apply default values for missing optional properties.
  • Return a formatted string such as Bob Smith - Admin (active).
  • Do not mutate the input object.
Take Quiz

Keep learning

Related questions

View more