TypeScript’s function parameter system goes well beyond what plain JavaScript offers. Three features — rest parameters, optional parameters, and default parameters — let you write functions that are flexible to call, self-documenting to read, and safe to maintain. Mastering all three is one of the fastest ways to level up your TypeScript code quality.
This guide walks through each feature in depth with fully annotated code, explains the type-system rules that govern them, highlights the mistakes beginners make, and closes with a single real-world function that combines all three.
1. Rest Parameters: Accepting a Variable Number of Arguments
A rest parameter collects any number of trailing arguments into a single typed array. You define one by prefixing the last parameter name with ... (three dots). At runtime, the rest parameter is an ordinary JavaScript array — TypeScript just enforces the element type at compile time.
Syntax
function functionName(...args: ElementType[]): ReturnType {
// 'args' is always an array — never undefined
}
Example: Sum any number of numbers
// sumAll() accepts zero or more numbers and returns their total.
// The '...numbers' rest parameter collects all arguments into a number[].
function sumAll(...numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0); // reduce over the array
}
console.log(sumAll()); // Output: 0 (empty array — reduce returns initialValue)
console.log(sumAll(1, 2)); // Output: 3
console.log(sumAll(10, 20, 30)); // Output: 60
console.log(sumAll(5, 5, 5, 5, 5)); // Output: 25
Example: Tagged log messages
Rest parameters work naturally alongside regular required parameters — the rest parameter just has to come last.
// 'level' is a required string; '...messages' collects all remaining arguments.
function logMessage(level: string, ...messages: string[]): void {
const combined = messages.join(' | ');
console.log(`[${level.toUpperCase()}] ${combined}`);
}
logMessage('info', 'Server started', 'Port 3000');
// Output: [INFO] Server started | Port 3000
logMessage('error', 'Connection refused');
// Output: [ERROR] Connection refused
Rules for rest parameters
- There can be at most one rest parameter per function.
- It must be the last parameter in the signature — TypeScript rejects anything placed after it.
- Its type must be an array type (e.g.,
string[],number[],Array<T>). - When no extra arguments are passed it is an empty array, never
undefined.
2. Optional Parameters: Not Every Argument Is Required
An optional parameter is declared by appending ? directly after the parameter name. When the caller omits it, its value inside the function is undefined — TypeScript widens its type to T | undefined automatically. You are then responsible for narrowing before use.
Syntax
function functionName(required: Type, optional?: Type): ReturnType {
// Inside the function, 'optional' has type: Type | undefined
if (optional !== undefined) {
// narrowed to Type — safe to use
}
}
Example: Creating a user profile
// The User interface marks email as optional using '?'.
// Our function mirrors this — email is optional on the way in too.
interface User {
firstName: string;
lastName: string;
email?: string; // '?' means the property may be absent from the object
}
function createUser(
firstName: string,
lastName: string,
email?: string // optional — caller may omit it
): User {
const user: User = { firstName, lastName };
// Falsy check — skips empty string too, not just undefined
if (email) {
user.email = email;
}
return user;
}
// 1. Required parameters only
const alice = createUser('Alice', 'Brown');
console.log(alice);
// Output: { firstName: 'Alice', lastName: 'Brown' }
// 2. All three parameters
const bob = createUser('Bob', 'Smith', '[email protected]');
console.log(bob);
// Output: { firstName: 'Bob', lastName: 'Smith', email: '[email protected]' }
Optional vs. union with undefined
These two signatures look similar but behave differently at the call site:
// Optional: caller may omit the argument entirely
function a(x?: string): void {}
a(); // OK
a(undefined); // OK
a('hello'); // OK
// Union: caller must pass *something* — even if that something is undefined
function b(x: string | undefined): void {}
b(); // Error: Expected 1 argument
b(undefined); // OK
b('hello'); // OK
Rules for optional parameters
- Optional parameters must appear after all required parameters.
- TypeScript infers the type as
T | undefined— always narrow before use. - You cannot combine
?with a non-undefineddefault in the same declaration — use a default parameter instead (see below).
3. Default Parameters: Automatic Fallback Values
A default parameter specifies the value to use when the caller either omits the argument or explicitly passes undefined. TypeScript infers the type from the default value, so you rarely need an explicit type annotation when using defaults.
Syntax
// TypeScript infers 'param' as the type of 'defaultValue'
function functionName(param = defaultValue): ReturnType {
// 'param' is guaranteed to be defined — never undefined inside the function
}
Example: Applying a percentage discount
// discountRate defaults to 0.10 (10%) when not provided.
// TypeScript infers discountRate as 'number' from the literal 0.10.
function applyDiscount(price: number, discountRate = 0.10): number {
const saving = price * discountRate;
return price - saving;
}
// Omit discountRate — default 10% is applied
console.log(applyDiscount(200)); // Output: 180
// Pass 25% explicitly — overrides the default
console.log(applyDiscount(200, 0.25)); // Output: 150
// Pass undefined — also triggers the default
console.log(applyDiscount(200, undefined)); // Output: 180
Default parameters vs optional parameters — key difference
With ?, the parameter’s type inside the function is T | undefined — you must check. With = value, the parameter inside the function is always T — no check needed. Prefer defaults whenever you have a sensible fallback value.
Rules for default parameters
- The default value can be any expression — a literal, a function call, or an object.
- Unlike optional parameters, default parameters do not have to be at the end — but placing them before required ones is almost always a bad idea (callers must pass
undefinedto skip them). - TypeScript infers the type from the default value; add an explicit annotation only if inference is wrong.
4. Combining All Three: A Real-World Example
A single function can use all three parameter kinds together. The signature rule is: required first, default or optional in the middle, rest parameter last.
/**
* Builds a formatted product listing string.
*
* @param name Required — the product name.
* @param price Required — the price as a number.
* @param currency Default 'USD' — overridable per region.
* @param description Optional — a short marketing blurb.
* @param tags Rest — zero or more search keywords.
*/
function buildListing(
name: string, // required
price: number, // required
currency = 'USD', // default parameter
description?: string, // optional parameter
...tags: string[] // rest parameter — must be last
): string {
let lines: string[] = [
`Product : ${name}`,
`Price : ${price} ${currency}`,
];
if (description) {
lines.push(`Desc : ${description}`);
}
if (tags.length > 0) {
lines.push(`Tags : ${tags.join(', ')}`);
}
return lines.join('n');
}
// --- Call 1: required only ---
console.log(buildListing('Coffee Mug', 12));
// Product : Coffee Mug
// Price : 12 USD
// --- Call 2: override currency ---
console.log(buildListing('T-Shirt', 25, 'EUR'));
// Product : T-Shirt
// Price : 25 EUR
// --- Call 3: add description (pass undefined to skip currency default) ---
console.log(buildListing('Laptop Stand', 45, undefined, 'Ergonomic aluminium stand.'));
// Product : Laptop Stand
// Price : 45 USD
// Desc : Ergonomic aluminium stand.
// --- Call 4: all features ---
console.log(buildListing('Wireless Mouse', 30, 'USD', 'Quiet clicks.', 'tech', 'office'));
// Product : Wireless Mouse
// Price : 30 USD
// Desc : Quiet clicks.
// Tags : tech, office
Quick-Reference: Rules at a Glance
| Feature | Syntax | Position rule | Type inside function | Triggered by |
|---|---|---|---|---|
| Required | p: T | First | T | Any value |
| Default | p: T = v | After required (best practice) | T (never undefined) | Omit or pass undefined |
| Optional | p?: T | After required | T | undefined | Omit |
| Rest | ...p: T[] | Last — only one allowed | T[] (never undefined) | Any trailing arguments |
See Also
- Parameterized Tests in JUnit 6 — using arrays as argument sources
- Mastering Credit Card Validation with Regular Expressions in Java
Conclusion
TypeScript’s three parameter modifiers each solve a distinct design problem. Use rest parameters when the number of arguments is genuinely variable. Use optional parameters when a caller may legitimately have nothing to pass and you will handle undefined inside the function. Use default parameters when there is a sensible fallback value — this is almost always cleaner than an optional because the function body is guaranteed to receive a real value without any null-check.
Keep the ordering rule in mind — required → default/optional → rest — and you will never get a TypeScript compiler error on a function signature again.