Module 3 · Functions & Scope Mastery ⏱ 19 min

Default, Rest & Spread Parameters

By the end of this lesson you will be able to:
  • Use default values for function parameters
  • Collect arguments with rest parameters
  • Spread arrays and objects into new collections

Modern JavaScript gives you three tools for flexible arguments, and real codebases lean on all three daily. Default parameters fill in missing values, so callers can omit arguments they do not always need. Rest parameters gather every remaining argument into a real array, letting one function accept any number of inputs. Spread syntax expands a collection into separate items, whether you are calling a function, building an array, or cloning an object.

Together these replace most of the old arguments object hacks and manual array slicing. They make functions more readable at the call site and more robust at the definition site — and they share one piece of syntax, the three dots ..., which is exactly where beginners get tripped up, because the meaning of those dots flips depending on where you write them.

flowchart TD
  DEF["default params"] --> F1["fill missing values"]
  REST["rest ...args"] --> F2["collect into array"]
  SPR["spread ...arr"] --> F3["expand elements"]
  style DEF fill:#e0900b,color:#fff
  style REST fill:#e0900b,color:#fff
  style SPR fill:#e0900b,color:#fff
Default fills missing args; rest collects extras; spread expands a collection.

Default parameters

A default parameter assigns a fallback value when the argument is missing or explicitly undefined. It goes right in the function header:

function greet(name, greeting = "Hello") {
  return `${greeting}, ${name}`;
}

Now greet("Ada") returns "Hello, Ada", and greet("Ada", "Hi") returns "Hi, Ada". The default is skipped only when the argument is missing or undefined; passing null, 0, or "" uses those values instead.

Defaults are evaluated left to right, so a later parameter can use an earlier one:

function greet(name, greeting = "Hello, " + name) { ... }

One subtle rule: once a parameter has a default, every parameter to its right must also have one. JavaScript evaluates arguments positionally, and it would be ambiguous where to place a trailing positional argument if some earlier parameter were optional.

Defaults fill in missing arguments; undefined triggers the default, null does not.
function greet(name, greeting = "Hello") {
  return `${greeting}, ${name}`;
}
console.log(greet("Ada"));
console.log(greet("Ada", "Hi"));
console.log(greet("Ada", undefined));
console.log(greet("Ada", null));

The default trap: undefined versus null

Beginners assume a default kicks in for any "empty" value. It does not — it kicks in for exactly one of them. Pass null, 0, an empty string, or false, and JavaScript keeps what you gave it; only a missing argument or a literal undefined lets the fallback fire. The reason is that null and undefined are different ideas in this language: null is an intentional absence, while undefined marks a slot that was never filled.

So a function that defaults a discount applies the default when you write price(item) or price(item, undefined), yet charges full price when you write price(item, null), because you told it, on purpose, that there is no discount.

flowchart LR
  A["sum(1, 2, 3)"] --> B["a = 1"]
  A --> C["b = 2"]
  A --> D["...nums = [3]"]
  style D fill:#e0900b,color:#fff
Rest collects all remaining arguments into a single array parameter.

Rest parameters

Rest parameters gather the remaining arguments into a real array. They use the same ... syntax as spread, but in a parameter list they mean "collect everything else here":

function sum(...nums) {
  return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4);  // nums is [1, 2, 3, 4]

Rest is not an exotic feature — it is just a cleaner replacement for the old arguments object. Unlike arguments, a rest parameter is a real array, so you can call .map, .filter, and .reduce on it directly.

You can mix regular parameters with rest, but rest must be the last parameter in the list. function(a, b, ...rest) is valid; function(...rest, a) is a syntax error because the engine would not know where positional arguments end and the rest begin.

Rest combined with regular parameters: the rest array gets whatever is left over.
function join(separator = ", ", ...parts) {
  return parts.join(separator);
}
console.log(join(" - ", "a", "b", "c"));
console.log(join(undefined, "x", "y"));

function tag(first, ...rest) {
  console.log(first, rest);
}
tag("hello", "world", "!");

Spread syntax

Spread wears the same three dots as rest, but it works in reverse: it expands an array or object into individual elements. Use it in function calls, array literals, or object literals:

const nums = [1, 2, 3];
console.log(Math.max(...nums));   // same as Math.max(1, 2, 3)
const copy = [...nums];           // a new array with the same items

Spread is indispensable for immutability. Instead of pushing into an array — which mutates it — you spread the old array into a new one and add the extra item:

const next = [...prev, 4];  // prev is untouched

For objects, spread copies own enumerable properties into a new object. It is the standard way to merge objects or override specific keys:

const defaults = { theme: "dark", fontSize: 14 };
const user = { fontSize: 16 };
const settings = { ...defaults, ...user };  // theme: dark, fontSize: 16

When two spread objects share a key, the one written last wins — that is how fontSize: 16 from user overrides the default.

flowchart LR
  A["[1, 2, 3]"] --> S["...nums"]
  S --> B["Math.max(1, 2, 3)"]
  S --> C["[0, ...nums, 4]
[0,1,2,3,4]"]
  style B fill:#e0900b,color:#fff
  style C fill:#e0900b,color:#fff
Spread expands a collection into individual elements at the call site or in a literal.
Spread for immutability: build new arrays and objects without mutating the old ones.
const arr1 = [1, 2];
const arr2 = [3, 4];
console.log([0, ...arr1, ...arr2]);

const base = { a: 1, b: 2 };
const override = { b: 9, c: 3 };
console.log({ ...base, ...override });

const items = ["x", "y"];
const more = [...items, "z"];
console.log(items);
console.log(more);

The same dots, two opposite jobs

... is the only new syntax in this lesson, and that is the whole trap, because it does opposite things depending on where it sits. Inside a parameter list, on the receiving end of a call, it gathers many arguments into one array — that is rest. Inside a call or a literal, on the giving end, it spreads one array or object out into many separate elements.

Read it as collect the rest here when it is a parameter, and as empty this array out here when it is an argument. If a line ever feels backwards, ask which side of the call it lives on, and the meaning snaps into place.

Exercise

What does this print? Spread expands the array into individual arguments.

function maxOf(first, ...rest) {
  return Math.max(first, ...rest);
}
console.log(maxOf(3, 1, 4, 1));
Exercise

Write merge(base, ...overrides) that starts with base and spreads each object in overrides on top of it, left to right. Return the merged object. (Use object spread: {...obj1, ...obj2}.)

function merge(base, ...overrides) {
  // return merged object
}
Exercise

Write prepend(prefix, ...words) that returns a new array where each word is prefixed with prefix and a space. Use map on the rest array. So prepend("Hi", "Ada", "Linus") returns ["Hi Ada", "Hi Linus"].

function prepend(prefix, ...words) {
  // return array of prefixed strings
}
Exercise

This function is meant to copy an array and append a new item without mutating the original. It mutates original instead. Fix it using spread.

function append(original, item) {
  original.push(item);
  return original;
}
Exercise

Write defaults(target, ...sources) that copies all properties from each source object into target, left to right, using object spread. Earlier sources are overwritten by later ones. Return target.

function defaults(target, ...sources) {
  // merge all sources into target, return target
}

Recap

  • Omit an argument, or pass undefined, and the default fills in; pass null or 0 and your value is kept.
  • Rest (...name as a parameter) packs leftover arguments into a genuine array, and it must come last.
  • Spread (...name in a call or literal) unpacks an array or object into its separate pieces.
  • Reach for spread to clone arrays and merge objects without mutating the originals.
  • Every spread copy is one level deep: nested objects are shared, so editing one edits both.

Next you'll learn the difference between loose and strict equality, and why == has a reputation for surprises.

Checkpoint quiz

Which syntax collects the remaining function arguments into an array?

What does [...arr1, ...arr2] do?

Go deeper — technical resources