Command Palette

Search for a command to run...

Hectal
PHASE 3Beginner ES2015+ ~26 min· topic 3 of 9

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
...name as the last parameter: collects the remaining arguments into an array.
Spread
...value in 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.

defaults.jswhole filejavascript
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.

rest.jswhole filejavascript
function sum(...numbers) {
  let total = 0;
  for (const n of numbers) {
    total += n;
  }
  return total;
}
sum();        // 0
sum(1, 2, 3); // 6

03Spread an array into a call

You have an array but the function wants separate arguments. Spread unpacks it at the call.

spread.jswhole filejavascript
const scores = [10, 20, 30];
sum(...scores);        // same as sum(10, 20, 30)
Math.max(...scores, 99); // 99, spread mixes with normal arguments
Spread an array into a calldiagram
Rendering diagram…

04Meet 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.

arguments.jswhole filejavascript
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.

options.jswhole filejavascript
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 matter

Try it yourself

  1. 1

    Predict the defaults

    In the first example, add console.log(greet(0)) and console.log(greet(false, "Yo")). Predict both lines first: which arguments count as "missing"?

  2. 2

    Average with rest

    In the second example, write function average(...nums) that returns sum(...nums) / nums.length. What does average() return with no arguments, and why?

  3. 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

Default parameters: when they apply New tab

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
Rest, spread and the arguments object New tab

`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' ] ]
Destructured parameters build buttons New tab

Callers name only the options they care about, in any order. `= {}` makes `makeButton()` safe.

Preview

Press Run to see the page.

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().

terminal
$ Console
── what you'll see ──
Uncaught TypeError: Cannot destructure property 'label' of 'undefined' as it is undefined.

Break #2

Put the rest parameter first

Write function tag(...others, last) {}.

terminal
$ Console
── what you'll see ──
Uncaught SyntaxError: Rest parameter must be last formal parameter

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 arguments stops being mapped to the parameters (normally, in sloppy mode, assigning to a also changes arguments[0]). Strict mode never maps them.

  • ▸

    Spreading very large arrays into a call (Math.max(...hugeArray)) can throw RangeError: 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 or reduce for big data.

  • ▸

    fn.length stops counting at the first default or rest parameter, so function f(a, b = 2, c) {} has length 1.

  • ▸

    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. 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 as undefined explicitly. Other "empty" values do not trigger it: null, 0, "" and false are real values and are kept. (Before ES2015 people wrote name = name || "friend", which wrongly replaced 0 and "" too.)

  2. 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, and function 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. 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 a SyntaxError. You can mix it with normal parameters: in function tag(first, ...others), first gets the first argument and others gets the rest.

  4. 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 as Math.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. 5

    Every regular (non-arrow) function also gets an **arguments object**: all the arguments of the call, in order, with a length. It is array-like (it has indexes and length) but not an array, so it has no map or filter until you convert it with Array.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. 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 destructure undefined and throws a TypeError. Options objects beat long lists of positional arguments like makeButton("Save", undefined, "large").

Explain it without notes

01

When does a default parameter value get used, and when is it evaluated?

02

What is the difference between a rest parameter and spread syntax?

03

What is the arguments object, and why are rest parameters preferred today?

04

Why do APIs often accept a single destructured options object instead of many positional parameters?

Practice

01

Write power(base, exponent = 2) that returns base ** exponent. Test power(5) and power(2, 10).

02

Write joinWords(separator, ...words) so that joinWords("-", "a", "b", "c") returns "a-b-c".

03

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 undefined triggers them.

  • I can collect extra arguments with a rest parameter.

  • I can spread an array into a function call.

  • I can explain what arguments is and why rest parameters replaced it.

  • I can write a function that takes a destructured options object with defaults.