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:
firstNamemust exist and be a stringlastNameis optional- if
lastNameexists, 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:
- Destructuring with defaults
- Nullish coalescing (
??) - 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)0false
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
lastNamefield. If it exists, it must be a string." - Default: "If no
lastNamewas 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.
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
firstNameandlastName - 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:
firstNamebecomes'bob'lastNameis 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
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
| Approach | Example | Best for | Notes |
|---|---|---|---|
| Destructuring default | function f({ name = 'x' }: T) | Clean parameter handling | Usually the best choice |
| Nullish coalescing | const name = obj.name ?? 'x' | Defaults inside function body | Preserves valid falsy values |
| Logical OR | `const name = obj.name | 'x'` | |
| Object spread defaults | const final = { ...defaults, ...options } | Many option fields | Great for config objects |
|| vs ??
| Operator | Falls back when value is undefined | Falls back when value is null | Falls back when value is '' | Falls back when value is 0 |
|---|---|---|---|---|
| ` | ` | Yes | Yes | |
?? | Yes | Yes | No | No |
Destructuring default vs object spread
| Pattern | Example | Use when |
|---|---|---|
| Destructuring | function f({ a = 1 }: T) | You only need a few fields directly in the function |
| Object spread | const final = { a: 1, ...options } | You want a complete normalized config object |
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 or0should 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 };
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
firstNameproperty. - Make
lastName,role, andisActiveoptional. - Apply default values for missing optional properties.
- Return a formatted string such as
Bob Smith - Admin (active). - Do not mutate the input object.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.