Question
Consider this TypeScript class:
export class Thread {
id: string;
lastMessage: Message;
name: string;
avatarSrc: string;
constructor(
id?: string,
name?: string,
avatarSrc?: string
) {
this.id = id || uuid();
this.name = name;
this.avatarSrc = avatarSrc;
}
}
What does the ? mean in a constructor parameter such as id?: string?
Short Answer
The ? marks a TypeScript parameter as optional. A caller may omit that argument, and inside the function its value can be undefined. You will learn how optional parameters affect types, argument order, defaults, and class property assignments.
Concept
id?: string is an optional parameter.
It means both of these calls are valid:
new Thread("thread-1", "General", "/images/general.png");
new Thread();
TypeScript treats an optional parameter as having a type that includes undefined:
id?: string
// behaves like:
id: string | undefined
This matters because a value might not have been supplied. Before using it as a definite string, your code must either:
- provide a fallback value,
- check whether it is defined, or
- use a default parameter value.
Optional parameters are useful when an argument is genuinely not required for the function or constructor to do its job. In this class, an ID can be generated when one is not supplied.
Mental Model
Think of a function call as filling out a form.
A parameter without ? is a required field: you must fill it in.
function createUser(name: string) {}
A parameter with ? is an optional field: you may leave it blank.
function createUser(name?: string) {}
When the field is left blank, the function receives undefined. Your code must decide what to do with that missing value: use a default, skip an action, or report an error.
Syntax and Examples
Optional parameters place ? after the parameter name and before its type:
function greet(name?: string): string {
if (name === undefined) {
return "Hello, guest!";
}
return `Hello, ${name}!`;
}
console.log(greet("Ava")); // "Hello, Ava!"
console.log(greet()); // "Hello, guest!"
A shorter approach is a default parameter:
function greet(name: string = "guest"): string {
return `Hello, ${name}!`;
}
For the Thread constructor, defaults make the intended behavior explicit:
export class Thread {
id: string;
name?: string;
avatarSrc?: string;
constructor(
id: string = uuid(),
name?: string,
avatarSrc?: string
) {
this.id = id;
this.name = name;
this.avatarSrc = avatarSrc;
}
}
Here, id is no longer optional from the constructor body's point of view: it will always be a string because uuid() is used when the caller omits it.
Step by Step Execution
Trace this constructor call:
const thread = new Thread(undefined, "Announcements");
Using this constructor:
constructor(
id: string = uuid(),
name?: string,
avatarSrc?: string
) {
this.id = id;
this.name = name;
this.avatarSrc = avatarSrc;
}
- The first argument is explicitly
undefined. - Because
idhas the default valueuuid(), TypeScript/JavaScript callsuuid()and assigns its returned string toid. - The second argument,
"Announcements", is assigned toname. - No third argument was provided, so
avatarSrcisundefined. - The properties receive these values:
thread.id; // a generated UUID string
thread.name; // "Announcements"
thread.avatarSrc; // undefined
Optionality applies to each argument by position. Omitting an earlier argument while supplying a later one requires passing undefined as a placeholder.
Real World Use Cases
Optional parameters appear whenever an API can use sensible defaults or an extra behavior is not always needed.
- Generated identifiers: Accept an existing record ID during imports, but generate one for newly created records.
- UI options: Let callers supply an optional avatar URL, CSS class, callback, or display label.
- Search filters: Accept optional filters such as category, page number, sort order, or date range.
- Logging: Allow an optional metadata object when recording an event.
- API clients: Let callers provide optional request options, such as a timeout or authorization header.
Example with an options object:
type RequestOptions = {
timeoutMs?: number;
retry?: boolean;
};
function fetchPosts(options: RequestOptions = {}) {
const timeout = options.timeoutMs ?? 5_000;
const retry = options.retry ?? true;
return { timeout, retry };
}
Optional properties are especially convenient when several settings may be omitted independently.
Real Codebase Usage
In production code, optional parameters are often combined with clear defaults, validation, and guard clauses.
Prefer default values when a value is always needed
function createThread(id: string = uuid()): string {
return id;
}
This states that the function always returns a usable ID.
Use guard clauses when omission is meaningful
function displayAvatar(avatarSrc?: string): string {
if (!avatarSrc) {
return "/images/default-avatar.png";
}
return avatarSrc;
}
Prefer an options object for many optional values
Positional optional arguments become hard to read:
createThread(undefined, "General", "/avatar.png");
An options object is clearer and easier to extend:
type ThreadOptions = {
id?: string;
name?: string;
avatarSrc?: string;
};
class Thread {
id: string;
name?: string;
avatarSrc?: string;
constructor({ id = uuid(), name, avatarSrc }: ThreadOptions = {}) {
this.id = id;
this.name = name;
this.avatarSrc = avatarSrc;
}
}
const thread = new Thread({ name: "General" });
This pattern is common for constructors, framework configuration, and functions with multiple optional settings.
Common Mistakes
Assigning an optional value to a required property
In the original class, name and avatarSrc are declared as string, but the constructor parameters can be undefined.
class Thread {
name: string;
constructor(name?: string) {
this.name = name; // Error with strictNullChecks enabled
}
}
Fix it by making the property optional:
name?: string;
Or give it a default:
this.name = name ?? "Untitled";
Using || when an empty string is valid
this.id = id || uuid();
This generates a new ID when id is "", because an empty string is falsy. If only null and undefined should trigger the fallback, use ??:
this.id = id ?? uuid();
Putting a required parameter after an optional parameter
This is not allowed because callers could not reliably tell which argument they are omitting:
// Invalid
function connect(host?: string, port: number) {}
Put required parameters first, or use defaults/options:
function connect(port: number, host: string = "localhost") {}
Assuming optional means null
An omitted optional parameter becomes undefined, not null. A caller can still explicitly pass null only if the type allows it:
function setLabel(label?: string) {}
setLabel(); // valid: undefined
// setLabel(null); // invalid with strictNullChecks
Forgetting required class properties
lastMessage: Message; must be initialized in the constructor, declared optional, or definitely assigned later. With strict property initialization enabled, leaving it unassigned produces an error.
Comparisons
| Feature | Meaning | Example | Best use |
|---|---|---|---|
| Optional parameter | Caller may omit the argument; value can be undefined | id?: string | An input is not always needed |
| Default parameter | Uses a value when the caller passes or omits undefined | id: string = uuid() | The function always needs a usable value |
Union with undefined | Explicitly permits undefined, but does not make omission optional | id: string | undefined | The caller must pass an argument positionally |
| Optional property | An object property may be absent | avatarSrc?: string | Data fields that are not always available |
| Nullable value | Value may be null | id: string | null | null has a distinct meaning, such as “intentionally cleared” |
An important difference:
function optionalValue(value?: string) {}
function requiredPosition(value: string | undefined) {}
optionalValue(); // valid
requiredPosition(); // error: an argument is required
requiredPosition(undefined); // valid
Cheat Sheet
// Optional parameter
function save(title?: string) {}
// Equivalent value type inside the function
// title: string | undefined
// Default parameter
function save(title: string = "Untitled") {}
// Optional property
interface ThreadData {
avatarSrc?: string;
}
// Fall back only for null or undefined
const actualId = id ?? uuid();
// Fall back for all falsy values, including "", 0, and false
const actualId = id || uuid();
Rules:
- Write
name?: Type, notname: ?Type. - Optional parameters normally come after required parameters.
- An omitted optional argument is
undefined. - Check for
undefinedor provide a default before treating an optional value as definitely present. - If a property can receive an optional parameter directly, make the property optional or assign a fallback value.
- Use an options object when a function has several optional settings.
FAQ
What does ? mean after a TypeScript parameter name?
It makes the parameter optional. The caller can omit that argument, and the parameter may be undefined inside the function.
Is id?: string the same as id: string | undefined?
Inside the function, both values can be strings or undefined. At the call site, they differ: id?: string allows the argument to be omitted, while id: string | undefined requires an argument, even if it is undefined.
Can an optional parameter have a default value?
Yes, but ? is unnecessary when you use a default:
function createId(id: string = uuid()) {}
A parameter with a default is optional for callers.
Should I use || or ?? for an optional value?
Use ?? when only null and undefined should use the fallback. Use || only when all falsy values, including an empty string, should use it.
Why does assigning name?: string to name: string cause an error?
The parameter might be undefined, but the property promises it is always a string. Make the property optional or provide a string default.
Can I omit the second optional argument but provide the third one?
Yes, but pass undefined for the skipped position:
new Thread(undefined, undefined, "/avatar.png");
For readability, consider an options object instead.
Does an optional parameter accept null?
Not by default with strictNullChecks enabled. Use string | null | undefined if null should be accepted as well.
Mini Project
Description
Build a small Thread model for a messaging application. The constructor should accept optional thread details while ensuring every thread receives an ID and a usable display name.
Goal
Create Thread instances with optional constructor inputs and safe default values.
Requirements
Use an optional id input and generate an ID when it is missing.
Use an optional name and default it to "Untitled thread".
Allow an avatar URL to be omitted.
Create and log one thread with supplied values and one with no values.
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.