Topic 3.3
Default, Rest Parameters and Spread
In one line
Default parameters fill in missing arguments, rest parameters (...items) collect any number of arguments into an array, spread (...array) does the opposite at the call, and destructured parameters pull named options straight out of an object; the old arguments object still exists in regular functions.
Think of it like this
Ordering at a juice stall. If you don't say a size, you get a medium (a default). If you say "orange, and also apple, mango and kiwi", the stall-keeper puts the first fruit on top and drops all the rest into one basket (a rest parameter). If you hand over a bag of fruit and say "use these", they tip the bag out one fruit at a time (spread). And if you hand over a filled-in order form, they read the boxes they care about by name (destructuring).
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Default parameter
- A value written in the definition, used when the argument is missing (
undefined). - Rest parameter
...nameas the last parameter: collects the remaining arguments into an array.- Spread
...valuein a call or a literal: unpacks an array (or other iterable) into separate items.- Iterable
- A value you can loop over with
for...of, such as an array, a string, a Map or a Set. - arguments
- An array-like object with every argument of a regular function call. Arrows don't have one.
- Array-like
- Has numbered items and a
length, but is not a real array and lacks array methods. - Destructuring
- Pulling values out of an object or array into variables in one step, like
{ label, size }. - Options object
- One object argument holding named settings, instead of many positional arguments.
Step by step
01Give a parameter a default
Write = value after the parameter. The default fills in only when the argument is undefined.
function greet(name = "friend") {
return "Hello, " + name + "!";
}
greet(); // "Hello, friend!"
greet(undefined); // "Hello, friend!"
greet(null); // "Hello, null!" (null is a real value)02Collect any number of arguments
A rest parameter turns "however many you pass" into one array you can loop over.
function sum(...numbers) {
let total = 0;
for (const n of numbers) {
total += n;
}
return total;
}
sum(); // 0
sum(1, 2, 3); // 603Spread an array into a call
You have an array but the function wants separate arguments. Spread unpacks it at the call.
const scores = [10, 20, 30];
sum(...scores); // same as sum(10, 20, 30)
Math.max(...scores, 99); // 99, spread mixes with normal arguments04Meet the old arguments object
Inside a regular function, arguments holds everything passed. It looks like an array but isn't one, so convert it before using array methods. You'll mostly see it in older code.
function old() {
console.log(arguments.length); // 2
return Array.from(arguments).join("-");
}
old("a", "b"); // "a-b"05Take an options object
Destructure the properties you need in the parameter list, with defaults for each, and = {} so the function also works with no argument.
function makeButton({ label, color = "steelblue", size = "medium" } = {}) {
return label + " (" + color + ", " + size + ")";
}
makeButton({ label: "Save" }); // "Save (steelblue, medium)"
makeButton({ size: "large", label: "Delete" }); // order doesn't matterTry it yourself
- 1
Predict the defaults
In the first example, add
console.log(greet(0))andconsole.log(greet(false, "Yo")). Predict both lines first: which arguments count as "missing"? - 2
Average with rest
In the second example, write
function average(...nums)that returnssum(...nums) / nums.length. What doesaverage()return with no arguments, and why? - 3
Remove the safety net
In the third example, delete the
= {}at the end of the parameter list and run it. Read the error in the console, then put it back.
Code & diagrams
Only `undefined` triggers a default. `nextId(50)` doesn't need the default, so `++calls` doesn't run for it.
Console
(nothing was printed)Expected console output
Hello, friend!
Hello, Asha!
Hi, Asha!
Hey, friend!
Hello, null!
Hello, !
110 100
1 2 50 3`Math.max(scores)` gets one argument, an array, which becomes `NaN`. Spread is the fix.
Console
(nothing was printed)Expected console output
0
6
60
99
NaN
[ 'h', 'e', 'y' ]
a + 2 more
[ 2, false, [ 'x', 'y' ] ]Callers name only the options they care about, in any order. `= {}` makes `makeButton()` safe.
Preview
Console
Nothing logged yet.Expected console output
Save (steelblue, medium)
Delete (crimson, large)
Tiny (seagreen, small)
Button (steelblue, medium)Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Call an options function with nothing
Write function makeButton({ label, size }) { ... } (no = {}) and call makeButton().
Break #2
Put the rest parameter first
Write function tag(...others, last) {}.
Myth vs fact
Myth
A default parameter replaces any falsy argument.
Fact
Only undefined triggers it. null, 0, "" and false are passed through unchanged.
Myth
Rest and spread are the same thing.
Fact
Same ... syntax, opposite jobs: rest gathers values into an array (in a definition), spread scatters an iterable into separate values (in a call or literal).
Myth
arguments is an array.
Fact
It's array-like: it has indexes and length but no array methods. Use Array.from(arguments) or, better, a rest parameter.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
As soon as a function has any default, rest or destructured parameter, its parameter list gets its own scope, and in sloppy mode
argumentsstops being mapped to the parameters (normally, in sloppy mode, assigning toaalso changesarguments[0]). Strict mode never maps them. - ▸
Spreading very large arrays into a call (
Math.max(...hugeArray)) can throwRangeError: Maximum call stack size exceeded, because every element becomes a real argument. Engines cap argument counts (in the order of 100,000 or more depending on the engine); use a loop orreducefor big data. - ▸
fn.lengthstops counting at the first default or rest parameter, sofunction f(a, b = 2, c) {}haslength1. - ▸
Destructured options objects are the standard API shape in the platform itself (
addEventListener(type, fn, { once: true }),fetch(url, { method: "POST" })), because new options can be added without breaking existing callers.
Remember this
- 1
A default parameter gives a parameter a value to use when the argument is missing:
function greet(name = "friend") { ... }. The default is used only when the argument is **undefined**, either left out or passed asundefinedexplicitly. Other "empty" values do not trigger it:null,0,""andfalseare real values and are kept. (Before ES2015 people wrotename = name || "friend", which wrongly replaced0and""too.) - 2
Defaults are expressions evaluated at call time, each time the default is needed, not once when the function is defined. So
function stamp(time = Date.now())gets a fresh time on every call, andfunction add(item, list = [])gets a brand-new empty array each call (in Python, by contrast, the default list is shared). A default can use earlier parameters,function price(amount, tax = amount * 0.1), but not later ones: those are still in the temporal dead zone (Topic 3.5). - 3
A rest parameter is written with three dots before the last parameter:
function sum(...numbers). It collects all remaining arguments into a real array, which is empty if there are none. It must be the last parameter, and there can be only one:function f(...a, b)is aSyntaxError. You can mix it with normal parameters: infunction tag(first, ...others),firstgets the first argument andothersgets the rest. - 4
Spread uses the same three dots, but at the call (or inside array and object literals): it unpacks an array, or any iterable (something you can loop over with
for...of, Topic 2.7, like a string or a Set), into separate arguments.Math.max(...[3, 9, 4])is the same asMath.max(3, 9, 4). Rule of thumb: dots in the definition gather (rest); dots in a call scatter (spread). You'll use spread to copy arrays ([...list]) and objects ({ ...settings }, ES2018) all the time. - 5
Every regular (non-arrow) function also gets an **
argumentsobject**: all the arguments of the call, in order, with alength. It is array-like (it has indexes andlength) but not an array, so it has nomaporfilteruntil you convert it withArray.from(arguments). It was the only way to accept "any number of arguments" before rest parameters existed. Arrow functions don't have their own. In new code, prefer rest parameters: they're a real array, they have a name that says what they contain, and they work in arrows. - 6
Destructured parameters unpack an object (or array) right in the parameter list:
function makeButton({ label, color = "steelblue", size = "medium" } = {}). The caller passes one options object and names what they set, in any order:makeButton({ label: "Save", size: "large" }). Each property can have its own default, and the final= {}makes calling with no argument at all safe; without it,makeButton()tries to destructureundefinedand throws aTypeError. Options objects beat long lists of positional arguments likemakeButton("Save", undefined, "large").
Explain it without notes
When does a default parameter value get used, and when is it evaluated?
What is the difference between a rest parameter and spread syntax?
What is the arguments object, and why are rest parameters preferred today?
Why do APIs often accept a single destructured options object instead of many positional parameters?
Practice
Write power(base, exponent = 2) that returns base ** exponent. Test power(5) and power(2, 10).
Write joinWords(separator, ...words) so that joinWords("-", "a", "b", "c") returns "a-b-c".
Write formatPrice({ amount, currency = "₹", decimals = 2 } = {}) that returns the currency followed by the amount with that many decimals, e.g. "₹49.90".
Trade-offs
- ↔
Positional parameters are shortest for one to three obvious inputs (
add(a, b)); past that, an options object is clearer and easier to extend. - ↔
Rest parameters accept anything, which is flexible but moves validation into the function: check the count and types when it matters.
Done when you can
I can give parameters defaults and know only
undefinedtriggers them.I can collect extra arguments with a rest parameter.
I can spread an array into a function call.
I can explain what
argumentsis and why rest parameters replaced it.I can write a function that takes a destructured options object with defaults.