A rest parameter in TypeScript, written as …name: Type[], collects any number of arguments into one typed array, so a function such as sum(37, 35, 40) accepts any count of values, including none. The opposite operation is the spread argument, where sum(…ages) expands an existing array into separate arguments. Together with function types, which describe a function’s parameters and return value as a type, rest parameters and spread arguments decide how we can call a TypeScript function and what the compiler checks at each call.
We use a rest parameter when a function takes a list of values of the same kind, such as a log() function with any number of messages or a sum() over the item prices in a shopping cart. A spread argument passes an array we already have, for example the scores from an API response, to such a function or to Math.max().
The following example shows a function type, optional and default parameters, a rest parameter, and spread arguments with arrays and tuples. The snippets compile under TypeScript 7.0 with strict turned on, and their output comes from Node.js 22.
// 1. Function type
type Greet = (name: string) => string;
const greet: Greet = (name) => "Hi, " + name;
const hi = greet("Lokesh"); // hi = "Hi, Lokesh"
// 2. Optional and default parameters
function label(name: string, age?: number, city = "Delhi"): string {
return name + " " + (age ?? "?") + " " + city;
}
const l1 = label("Raj"); // l1 = "Raj ? Delhi"
const l2 = label("Lokesh", 37, "Pune"); // l2 = "Lokesh 37 Pune"
// 3. Rest parameter collects the arguments into an array
function sum(...numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0);
}
const none = sum(); // none = 0
const total = sum(37, 35, 40); // total = 112
// 4. Fixed parameters before the rest parameter
function introduce(greeting: string, ...names: string[]): string {
return greeting + " " + names.join(", ");
}
const text = introduce("Hi", "Lokesh", "Raj"); // text = "Hi Lokesh, Raj"
// 5. Spread an array into a call
const ages = [37, 35, 40];
const all = sum(...ages); // all = 112
const oldest = Math.max(...ages); // oldest = 40
// 6. Tuple type for the rest parameter
function describe(...args: [name: string, age: number]): string {
return args[0] + " is " + args[1];
}
const info = describe("Lokesh", 37); // info = "Lokesh is 37"
Notice that sum() with no arguments returns 0, because the rest parameter numbers receives an empty array, not undefined.
We start with the ways to declare a function and write its type, and after that we look at rest parameters in depth, from the rules and readonly rest parameters to tuple types with labels and typed argument forwarding. Optional and default parameters get a short section here, and their full rules for optional and default parameters have their own article.
1. Function Declarations, Expressions and Function Types
TypeScript functions are JavaScript functions with types on the parameters and the return value. We can write a function in one of the following forms.
- A function declaration
- A function expression assigned to a variable
- An arrow function
A function type such as (name: string) => string describes the shape of a function, so a variable or an array element of that type can hold any function with that shape. For example, a form validator can keep a list of check functions of one function type and run each one against the user’s input.
// 1. Function declaration
function greet(name: string): string {
return "Hi, " + name;
}
// 2. Function expression and arrow function
const greetAgain = function (name: string): string {
return "Hi again, " + name;
};
const shout = (name: string): string => name.toUpperCase();
// 3. Function type alias
type NameFormatter = (name: string) => string;
const formatters: NameFormatter[] = [greet, greetAgain, shout];
const results = formatters.map((f) => f("Raj")); // results = ["Hi, Raj", "Hi again, Raj", "RAJ"]
All three functions fit NameFormatter, because each takes one string and returns a string. The parameter names in a function type are only labels, so f could use any name for its parameter. A function declaration is hoisted, so we can call it above the line where it is written. A function expression or arrow function exists only after its const line runs, because of the way variable hoisting treats const.
The old advice to type function variables as Function is outdated, because the Function type accepts any function and returns any when called, so the compiler checks nothing. The function type expression syntax keeps every check.
2. Optional and Default Parameters in Brief
By default, every declared parameter is required, and calling a function with fewer arguments is a compile error. A ? after the parameter name makes it optional, and its type inside the function includes undefined. A default value (city = “Delhi”) makes the parameter optional too, and the function receives the default when the argument is missing.
function label(name: string, age?: number, city = "Delhi"): string {
return name + " " + (age ?? "?") + " " + city;
}
const l1 = label("Raj"); // l1 = "Raj ? Delhi"
const l2 = label("Lokesh", 37); // l2 = "Lokesh 37 Delhi"
const l3 = label("John", 40, "Pune"); // l3 = "John 40 Pune"
The ?? operator returns its right side when age is undefined. A booking form is a typical case, where the guest name is required and the city falls back to the hotel’s own city. An optional parameter must come after the required ones, and a function with many optional values reads better with an options object with defaults.
3. TypeScript Rest Parameters
Optional parameters work when a function takes one or two extra values. When the number of arguments is open, such as a sum() of any count of numbers, we use a rest parameter. Three dots before the parameter name collect all remaining arguments into a real array, and the type annotation says what the array contains. For example, a shopping cart service can offer addItems(…names: string[]), so the page adds one item or ten items with the same call.
// 1. All arguments go into one array
function sum(...numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0);
}
const none = sum(); // none = 0
const two = sum(5, 3); // two = 8
const three = sum(37, 35, 40); // three = 112
// 2. Fixed parameters come first
function introduce(greeting: string, ...names: string[]): string {
return greeting + " " + names.join(", ");
}
const text = introduce("Hi", "Lokesh", "Raj"); // text = "Hi Lokesh, Raj"
const alone = introduce("Hi"); // alone = "Hi "
// 3. Union element type
function joinAll(...parts: (string | number)[]): string {
return parts.join("-");
}
const key = joinAll("apple", 5, "banana", 3); // key = "apple-5-banana-3"
A call with no extra arguments gives an empty array, never undefined, so sum() returns 0 and introduce(“Hi”) returns “Hi ” with a trailing space. When a function needs at least one value, we declare that value as a normal parameter before the rest parameter, for example max(first: number, …others: number[]). The compiler checks each argument against the element type, so the call sum(5, “3”) does not compile.
3.1. Rules for Rest Parameters
A rest parameter follows the rules that come from JavaScript, and TypeScript adds a type check on top of them.
- A function has at most one rest parameter.
- The rest parameter is the last parameter.
- Its type is an array or tuple type.
Breaking a rule gives a compile error, and the TypeScript compiler names the rule in the message.
function wrong(...names: string[], greeting: string) -> error TS1014: A rest parameter must be last in a parameter list.
function wrong2(...names: string) -> error TS2370: A rest parameter must be of an array type.
A rest parameter cannot have a default value, and it is never optional, because it is always an array. The length property of the function (sum.length) does not count the rest parameter.
3.2. readonly Rest Parameters
The array a rest parameter receives is a new array created for each call, so changing it does not affect the caller. Still, a function that only reads its arguments can say so with readonly, and after that, adding or removing elements inside the function fails to compile.
function countFruits(...fruits: readonly string[]): number {
return fruits.length;
}
const n = countFruits("apple", "banana"); // n = 2
A call to fruits.push(“kiwi”) inside countFruits() gives error TS2339: Property ‘push’ does not exist on type ‘readonly string[]’.
4. Spreading Arrays and Tuples Into a Function Call
The same three dots work in the other direction at the call site, where a spread argument expands an array into separate arguments. Spreading is how we pass an existing array either to a function with a rest parameter or to a built-in function such as Math.max(). For example, a dashboard gets the response times of the last hour as an array and shows the slowest one with Math.max(…times). The same spread operator also copies arrays and objects.
// 1. Spread an array into a rest parameter
const ages = [37, 35, 40];
const total = sum(...ages); // total = 112
const more = sum(5, ...ages, 3); // more = 120
const oldest = Math.max(...ages); // oldest = 40
// 2. Spread into fixed parameters needs a tuple
function person(name: string, age: number): string {
return name + " is " + age;
}
const lokesh = ["Lokesh", 37] as const; // type readonly ["Lokesh", 37]
const p1 = person(...lokesh); // p1 = "Lokesh is 37"
const raj: [string, number] = ["Raj", 35];
const p2 = person(...raj); // p2 = "Raj is 35"
Step 2 is where many developers get stuck. Without as const or the tuple annotation, [“Lokesh”, 37] has the type (string | number)[], which is an array of any length with mixed elements. TypeScript cannot match an array of any length to one string followed by one number, so the call fails.
error TS2556: A spread argument must either have a tuple type or be passed to a rest parameter.
A tuple type such as [string, number] fixes the length and the type at each position, which is what fixed parameters need. Spreading into a rest parameter has no such problem, because the rest parameter accepts any length.
5. Tuple Types for Rest Parameters
A rest parameter can also have a tuple type instead of an array type, so the function accepts a fixed list of arguments. A tuple type helps when we pass the parameter list itself around as a value, for example when a job queue stores the arguments of a task and calls the task later. Labeled tuple elements, added in TypeScript 4.0, give each position a name that editors show in hints.
// 1. Labeled tuple: exactly a name and an age
function describe(...args: [name: string, age: number]): string {
return args[0] + " is " + args[1];
}
const d = describe("Lokesh", 37); // d = "Lokesh is 37"
// 2. Optional tuple element
function tag(...args: [fruit: string, count?: number]): string {
const [fruit, count = 1] = args;
return fruit + " x" + count;
}
const t1 = tag("apple"); // t1 = "apple x1"
const t2 = tag("banana", 3); // t2 = "banana x3"
// 3. Reuse a function's parameter list
type DescribeArgs = Parameters<typeof describe>; // [name: string, age: number]
const args: DescribeArgs = ["John", 40];
const d2 = describe(...args); // d2 = "John is 40"
For callers, describe() looks the same as a function with two normal parameters, so the call describe(“Lokesh”) fails with TS2554: Expected 2 arguments, but got 1. The Parameters utility type in step 3 turns any function’s parameter list into such a tuple, so we can store or build arguments first and spread them into the call later.
6. Rest Parameters in Function Types and Wrappers
Function types can contain rest parameters, written the same way as in a declaration. The most useful case is a wrapper function that adds logging or retries around another function and forwards all arguments to it, such as an API client that logs the name of every call before it sends the request. A generic type parameter for the argument tuple keeps every argument type checked.
// 1. Rest parameter in a function type
type Logger = (level: string, ...messages: string[]) => void;
const log: Logger = (level, ...messages) => console.log("[" + level + "] " + messages.join(" "));
log("info", "apple", "banana"); // [info] apple banana
// 2. Forward any arguments to another function, fully typed
function withLog<A extends unknown[], R>(fn: (...args: A) => R, ...args: A): R {
console.log("calling " + fn.name);
return fn(...args);
}
function add(a: number, b: number): number {
return a + b;
}
const result = withLog(add, 5, 3); // result = 8
In withLog(), TypeScript infers A as [a: number, b: number] from add, so the remaining arguments must be two numbers. The call withLog(add, 5, “3”) fails with TS2345: Argument of type ‘string’ is not assignable to parameter of type ‘number’. A constraint such as A extends unknown[] is a rule for generic functions, and here it limits A to array and tuple types.
6.1. Rest Parameters vs the arguments Object
Older JavaScript code reads extra arguments from the arguments object. We avoid the arguments object in TypeScript for the following reasons.
- Its type is IArguments, not an array, so arguments.map() fails with TS2339: Property ‘map’ does not exist on type ‘IArguments’.
- Arrow functions do not have their own arguments, and at the top level of a module, the name gives TS2304: Cannot find name ‘arguments’.
- The function signature does not show that extra arguments are expected, so callers get a compile error when they pass them.
A rest parameter has none of these problems. It is a real typed array that works in arrow functions, and it is part of the signature, so callers see it.
7. Getting the Code From GitHub
We can clone the rest parameter examples and run them locally. The folder has a file for each numbered section, TypeScript 7.0.2 comes in as a dev dependency, and the runtime is Node.js 22 or later.
npm install
npm start
The console output repeats the results from the snippet comments, section by section. The invalid declarations and calls from section 3 to section 6 are in scratch files that are not committed, so the build stays green.
8. Conclusion
A function type describes how a function can be called, and parameters decide how many arguments it takes. A rest parameter collects a variable number of arguments into a typed array. It must be the last parameter, and it holds an empty array when no extra arguments are passed.
Spread arguments go the other way. An array spreads into a rest parameter as it is, whereas fixed parameters need a tuple, which as const or a tuple annotation provides. Tuple-typed rest parameters and Parameters<typeof fn> let us pass whole argument lists around with full type checks. For optional and default parameters, we continue with TypeScript Optional and Default Parameters.
9. References
MDN documents the JavaScript behavior of rest parameters, spread and arguments; the TypeScript Handbook adds the typing rules.
- TypeScript Handbook: Rest Parameters and Arguments
- TypeScript Handbook: Tuple Types
- TypeScript 4.0 release notes: Labeled Tuple Elements
- TypeScript: Utility Types (Parameters)
- MDN: Rest parameters
- MDN: Spread syntax
- MDN: The arguments object
Happy Learning !!
What is the difference between rest parameters and passing an array as optional parameter in function? Both does the same work
what if the rest params array have multiple types ?
It wouldn’t work, they have to be similar types.