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) can accept zero, three or a hundred values. The opposite operation is the spread argument: 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, they decide how a TypeScript function can be called and what the compiler checks at each call.
This tutorial starts with the ways to declare a function and to write its type. It then covers TypeScript rest parameters in depth: the rules, readonly rest parameters, spreading arrays and tuples into calls, tuple types with labels and optional elements, and typed argument forwarding. Optional and default parameters get a short section here; the article TypeScript Optional and Default Parameters covers them in full. The snippets compile under TypeScript 7.0 with strict turned on, and their output comes from Node.js 22.
A reader in a hurry needs only this snippet: it declares a function type, uses optional, default and rest parameters, and spreads arrays and tuples into calls.
// 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"
1. Function Declarations, Expressions and Function Types
TypeScript functions are JavaScript functions with types on the parameters and the return value. We can write them in three 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 variables, arrays and parameters can hold any function with that shape.
// 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; 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; the article on variable hoisting explains why.
The old advice to type such variables as Function is outdated. 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; its type inside the function then 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. Passing undefined versus leaving the argument out, the order rules, and options objects with defaults are covered in TypeScript Optional and Default Parameters. The rest of this article is about functions that take a variable number of arguments.
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.
// 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[]). Each argument is checked against the element type: sum(5, “3”) does not compile.
3.1. Rules for Rest Parameters
The rules come from JavaScript, and TypeScript adds a type check. A rest parameter has three rules:
- 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:
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. Adding or removing elements inside the function then 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 this function 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. A spread argument expands an array into separate arguments. This is how we pass an existing array to a function with a rest parameter, or to built-in functions such as Math.max(). The article on the spread operator covers spreading into 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)[]: an array of any length with mixed elements. TypeScript cannot match that to exactly one string and 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. The function then accepts an exact list of arguments, which is useful when the parameter list itself is passed around as a value. 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: 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. That lets us 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, timing or retries around another function and forwards all arguments to it. 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. withLog(add, 5, “3”) fails with TS2345: Argument of type ‘string’ is not assignable to parameter of type ‘number’. The article on generic functions explains type parameters and constraints such as A extends unknown[].
6.1. Rest Parameters vs the arguments Object
Older JavaScript code reads extra arguments from the arguments object. We avoid it in TypeScript for three 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; 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 solves all three: it is a real typed array, it works in arrow functions, and it is part of the signature.
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. Invalid declarations and calls from sections 3 to 6 live 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, must be the last parameter, and gives an empty array when no extra arguments are passed. Spread arguments go the other way: an array spreads into a rest parameter directly, while 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, 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.