← Fundamentals
Guided lesson 5 questions · 4 languages

Optional, default, and variadic parameters

One function, called with more or fewer arguments.

A function has one fixed list of parameters. A rest parameter collects any number of extra arguments into a new array, and a default gives a left-out parameter its value. A default replaces a missing argument or undefined, never null.

Before you begin

Why this matters

Functions you call every day, such as console.log and Math.max, take any number of arguments. If you know what a function receives, you can predict what it does with an empty call, with an array you already have, and with a value that is missing.

What this helps you reason about

  • Predict what a rest parameter holds, including in a call with no extra arguments.
  • Pass an array you already have to a rest parameter, and know that the function gets a new array.
  • Spot a default that is not used because the caller passed null.

A TypeScript function can take a changing number of arguments in two ways. A rest parameter collects extra arguments into an array, and an optional or default parameter lets a call leave an argument out. This lesson shows what each parameter holds in every call.

Already know this? The short version
  • A rest parameter, ...points: number[], must be last; it collects the arguments after the named parameters into a new array, [] when there are none.
  • A spread argument, f(...xs), passes each element separately; the rest parameter then holds a new array, so changing it never changes xs.
  • An optional parameter, x?: T, is undefined when left out. A default parameter, x = v, is used when the argument is left out or undefined, but not for null; ?? replaces both.
  • TypeScript checks the number of arguments when it compiles. Overload signatures only describe calls; one implementation handles them all.

Go to the final check· Go to the practice tickets

Words in this lesson (7)
argument
A value written in a call, such as 3 in area(3, 4). Each argument is computed before the function’s body starts.
parameter
A name in a function’s definition, such as width in function area(width, height). Each call creates its own parameters and gives them the argument values, matched by position.
rest parameter
The last parameter of a function, written with three dots, such as ...points: number[]. It collects every argument after the named parameters into a new array, which is empty when there are none.
spread argument
An array written with three dots in a call, such as total(...games). Each element is passed as a separate argument.
optional parameter
A parameter marked with ?, such as points?: number. A call may leave it out, and then the parameter holds undefined.
default parameter
A parameter with a value written in the definition, such as b: number = 10. The value is computed inside the call, when the argument is left out or is undefined.
null
A JavaScript value that code writes on purpose to mean “no value”. It is a different value from undefined.

Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.

01 / Any number

How can one function take any number of arguments?

Some functions take any number of arguments: console.log(1, 2) and console.log(1, 2, 3) both work. You can write such a function with a rest parameter. Write three dots before the last parameter’s name, as in ...points: number[].

A rest parameter collects every argument after the named parameters into a new array. In total(12, 9, 15), points is [12, 9, 15]. In total(), points is [], an empty array, never undefined. Inside the function it is an ordinary array: points.length counts the arguments, and for (const point of points) runs the loop body once for each of them.

Only the last parameter can be a rest parameter. The parameters before it are matched by position first: in best(first: number, ...others: number[]), the call best(7, 3) gives first 7 and others [3].

main.ts
function total(...points: number[]): number {
  let sum = 0;
  for (const point of points) {
    sum = sum + point;
  }
  return sum;
}
total(12, 9, 15); // 36: points is [12, 9, 15]
total(); // 0: points is []

A rest parameter is a new array of the extra arguments; with none, it is empty.

Coming from Python: *points collects extra arguments into a tuple, which cannot change; ...points collects them into an array, which the function can change.

Precisely: the arguments object

Older JavaScript code reads extra arguments through arguments, an object like an array that exists in every function declared with function. A rest parameter is a real array, it appears in the function’s signature, and TypeScript checks the type of each argument it collects. Arrow functions have no arguments object.

Experiment

Collect the arguments into one parameter

The last parameter collects every argument after the named ones. A call with three values gives it three elements; a call with none gives it no elements, so the loop body never runs and the sum stays 0.

Why it matters A function with a rest parameter must also work when the call passes no extra values at all.

Predict what points holds while total adds. Then add the line console.log(total()); after the first print and predict what points holds in that second call.

function total(...points: number[]): number {
  let sum = 0;
  for (const point of points) {
    sum = sum + point;
  }
  return sum;
}

console.log(total(12, 9, 15));
What to look for

Step into total. Its frame lists points as [12, 9, 15], a new array made for this call, and sum as not initialized yet until its line runs. sum then shows 12, 21, and 36, and the run prints 36. After the edit, a second total frame lists points as [], the loop body never runs, and the run prints 36, then 0. <top level> is the stepper’s name for the code outside every function, and the panel lists total as function total(...points). Step sometimes stops on the same line two or three times; that is normal for this debugger.

Expected output

36

02 / A list you have

What does the function receive when you pass a list you already have?

A rest parameter expects separate arguments. To pass an array you already have, spread it in the call: total(...games). The three dots in a call pass each element as a separate argument, so total(...[20, 18]) is total(20, 18). Without the dots, TypeScript rejects the call: total(games) passes one array where each argument must be a number.

The rest parameter then collects those arguments into a new array. So a function that changes its rest parameter does not change the caller’s array. Below, push adds an element at the end of points, and games keeps four elements. An ordinary array parameter is different: as in Arguments, values, and references, it reaches the caller’s array itself.

main.ts
function withExtra(...points: number[]): number[] {
  points.push(5);
  return points;
}

const games = [20, 18, 22, 17];
const longer = withExtra(...games);
console.log(longer.length, games.length); // 5 4

A spread argument passes each element separately; the rest parameter is a new array.

Coming from JavaScript: TypeScript checks a spread against the parameter types: f(...xs) with xs: number[] compiles for a rest parameter, but not for a function with exactly two number parameters, because the array’s length is not known.

Precisely: very long arrays

Every spread element becomes one argument of the call, and the engine limits how many arguments one call can have. In Node.js 22, Math.max(...values) with a million elements throws RangeError: Maximum call stack size exceeded. For long arrays, pass the array itself to a function that takes an array parameter.

Experiment

Spread an array into a rest parameter

withExtra(...games) writes each element of games as a separate argument. The rest parameter then collects them into a new array, so push adds 5 to that new array only. An ordinary array parameter receives a reference to the caller’s array itself.

Why it matters A function can change its rest parameter freely; the caller’s array is never that array. A function that takes an ordinary array parameter can change the caller’s array.

Predict both printed lines. Then remove the three dots in both places, so the parameter is points: number[] and the call is withExtra(games), and predict again.

function withExtra(...points: number[]): number[] {
  points.push(5);
  return points;
}

const games = [20, 18, 22, 17];
const longer = withExtra(...games);
console.log(longer.length, games.length);
console.log(longer === games);
What to look for

Step into withExtra. points shows [20, 18, 22, 17], and after push it shows [20, 18, 22, 17, 5], while <top level>’s games keeps [20, 18, 22, 17]. The run prints 5 4, then false: longer and games are two arrays. After the edit, push changes games too, and the run prints 5 5, then true. If you remove only the dots in the call, the program does not compile: Argument of type 'number[]' is not assignable to parameter of type 'number'.

Expected output

5 4
false

03 / Left out

What happens when an argument is left out?

TypeScript checks the number of arguments: a call that leaves out a required argument does not compile. Two kinds of parameter may be left out.

An optional parameter is marked with ?, as in points?: number. When a call leaves it out, it holds undefined, and TypeScript makes you handle undefined before you use the value as a number.

A default parameter, which Function calls introduced, has a value written in the definition: points: number | null = 2. The default is used when the argument is left out or is undefined. It is not used for null: a call that passes null gets null, as with a default in a pattern in Destructuring and multiple bindings. Below, a text in backquotes is a template literal: ${points} writes the value of points into the text. To replace null and undefined both, use points ?? 2.

main.ts
function shot(player: string, points: number | null = 2): string {
  return `${player} scores ${points}`;
}
shot("Ines"); // "Ines scores 2"
shot("Ines", null); // "Ines scores null"

A default replaces a left-out or undefined argument, never null.

Coming from Python: There are no keyword arguments, so a call cannot skip a middle parameter by name. Pass undefined in its place, or make the function take one object with named properties.

Precisely: defaults before other parameters

A default can use the parameters before it, as in function area(width: number, height = width). A parameter with a default that is not last can be skipped only by passing undefined in its place: area(undefined, 3) would use the default. A required parameter cannot come after an optional one.

Experiment

Pass null where a default is waiting

A default replaces a left-out argument, or undefined. null is a value the caller passed on purpose, so points holds null, and the template writes it as the text null. points ?? 2 gives 2 for null and for undefined.

Why it matters When data can hold null, a default parameter is not enough. Replace null inside the function with ??, or turn null into undefined before the call.

Predict the three printed lines. Then change ${points} to ${points ?? 2} and predict the last line again.

function shot(player: string, points: number | null = 2): string {
  return `${player} scores ${points}`;
}

console.log(shot("Ines"));
console.log(shot("Ines", 3));
console.log(shot("Ines", null));
What to look for

The panel lists shot as function shot(player, points = 2). At the stop in shot for the first call, points shows 2: the argument was left out, so the default was used. In the second call, points shows 3. In the third call, points shows null: the default was not used. The run prints Ines scores 2, Ines scores 3, and Ines scores null. After the edit, the panel still shows points as null in the third call, but the text uses 2, and the last line is Ines scores 2.

Expected output

Ines scores 2
Ines scores 3
Ines scores null

04 / Overloads

Can two functions share one name?

No. A name refers to one function, and a second function declaration with the same name does not compile. TypeScript reports Duplicate function implementation.

TypeScript does have overload signatures: several signature lines, without bodies, above one implementation. Each line describes one way to call the function, and the type checker uses them to check calls. They do not choose code. The one implementation receives every call and must tell the cases apart while it runs. Below, typeof value gives the name of the value’s kind, such as "number".

main.ts
function describe(value: number): string;
function describe(value: string): string;
function describe(value: number | string): string {
  if (typeof value === "number") {
    return `number ${value}`;
  }
  return `text ${value}`;
}

One name, one implementation: overload signatures only describe the calls.

Precisely: why one implementation

TypeScript removes types when it compiles to JavaScript, so nothing is left at run time to choose between two bodies by their parameter types. The implementation’s own signature is not visible to callers; only the overload signatures are.

05 / Try it

Can you predict a fresh call?

Follow which parameter each argument goes to. bonus has a default, and points is a rest parameter.

main.ts
function summary(team: string, bonus = 1, ...points: number[]): string {
  let sum = bonus;
  for (const point of points) {
    sum = sum + point;
  }
  return `${team} ${points.length} ${sum}`;
}

console.log(summary("Owls"));
console.log(summary("Owls", 10, 12));

What is printed?

Reveal the reasoning

Owls 0 1, then Owls 1 22. In the first call, bonus is left out, so its default, 1, is used, and points is []. In the second call, the parameters before the rest parameter are matched first, by position: 10 goes to bonus, not into points. points is [12], and the sum is 10 + 12 = 22.

Practice tickets

  1. PARAM-101 · A driver’s first lap never shows as the fastest → Free
  2. PARAM-102 · Drills with an empty speed field show no real speed → Sign in · Pro
  3. PARAM-103 · Workouts set to no rest still get a rest → Sign in · Pro

Keep exploring

Recursion →

Primary references