Learn by reading through in order

Default and Rest Parameters — Fallback Values and Collecting the Rest

Covers default parameters, rest parameters that collect arguments into an array, and the options object pattern.

Some arguments get the same value on almost every call, like a shipping fee or a tax rate. Leave one out and there's no error; you only notice it's missing when undefined shows up in the output.

This article covers default parameters, which set the value used for a left-out argument, rest parameters, which collect any number of arguments, and the options pattern, which passes settings as a single object.

Making Arguments Optional — Default Parameters

Say you have a function that builds a meeting room booking, where the duration is almost always 1 hour and the layout is almost always classroom style. If you still have to write all three arguments on every call, the one value you actually want to change gets lost in the call.

A default parameter (a way of setting in advance the value to use when an argument isn't passed) is written by adding = in the parameter list, as in hours = 1. A parameter with a default can be left out at the call site.

// Write a default by adding "= value" in the parameter list
const reserveRoom = (roomName, hours = 1, layout = "classroom") => {
  return `${roomName} / ${hours} hr / ${layout}`;
};

// If you leave out the 3rd, the default "classroom" is used
console.log(reserveRoom("Room A", 2));               // Room A / 2 hr / classroom

// When you pass a value, the default isn't used
console.log(reserveRoom("Room A", 2, "boardroom"));  // Room A / 2 hr / boardroom

// If you also leave out the 2nd, both defaults are used
console.log(reserveRoom("Room A"));                  // Room A / 1 hr / classroom
Arguments Fill In from the Left
Pass Room Ato roomNameNo defaultvalueroomName isRoom APass 2to hoursDefault 1isn't usedhours is 2Nothing passedfor layoutThe defaultkicks in herelayout isclassroom
This traces reserveRoom("Room A", 2) one parameter position at a time. Even with a default, it doesn't kick in when a value is passed.

You can only leave out arguments from the right, so put parameters with defaults last. Even if you only want to change the 3rd argument, you still have to write a value in the 2nd position.

Estimate shipping costs. boxCount and the per-box rates for each area are already declared, and shipping is the per-box rate × the number of boxes.

① Write a function that takes a box count and an area and returns the shipping, with 1 box and Honshu (Japan's main island) as the defaults.

② Leave out the area and display the shipping for boxCount boxes as "Honshu: X yen".

③ Specify Hokkaido as the area and display "Hokkaido: X yen".

④ Pass no arguments at all and display "Default: X yen".

(If it runs correctly, an explanation will appear.)

JavaScript / TypeScript Editor

Run code to see output

What a Missing Argument Gets — undefined and Defaults

If you leave out an argument for a parameter with no default, any calculation or string that uses it carries on anyway. Since nothing stops, you end up hunting afterward for the call that forgot to pass it.

A parameter that isn't passed an argument gets undefined. The default is used only when the argument is undefined — if you pass null, 0, or an empty string, the value you passed is used.

const notifyStatus = (orderId, status = "Received") => `${orderId}: ${status}`;

// Leaving it out and passing undefined both give you the default
console.log(notifyStatus("A-1021"));             // A-1021: Received
console.log(notifyStatus("A-1021", undefined));  // A-1021: Received

// null counts as a passed value, so the default isn't used
console.log(notifyStatus("A-1021", null));       // A-1021: null

// A left-out parameter with no default gets undefined
const plainStatus = (orderId, status) => `${orderId}: ${status}`;
console.log(plainStatus("A-1021"));              // A-1021: undefined
Leaving It Out vs. Passing null
Call withoutstatusstatus isundefined= "Received"appliesA-1021:ReceivedPass nullto statusstatus isnullThe defaultdoesn't applyA-1021:null
A left-out status gets undefined, so the default expression runs. null is treated as a passed value, so the default doesn't run.

The table below shows what ends up in status, and what gets displayed, for each second argument passed to notifyStatus. Passing undefined gives the same result as leaving the argument out, while null, just like a string, is displayed as passed.

CallValue in statusDisplayed
notifyStatus("A-1021")The default, ReceivedA-1021: Received
notifyStatus("A-1021", "Shipped")ShippedA-1021: Shipped
notifyStatus("A-1021", undefined)The default, ReceivedA-1021: Received
notifyStatus("A-1021", null)nullA-1021: null

Passing null Skips the Default

null is a value meaning "intentionally empty," so a default in the parameter list won't replace it. If you want the fallback even when an API response gives you null, skip the parameter default and use ?? inside the function instead, as in const shown = status ?? "Received";.

Display stock levels. products is already declared; it includes one item with a stock of 0, one with no stock key, and one with null.

① Write stockLabel, which takes a stock count and returns "stock: X", with "checking" as the default.

② Display every product in the form "[product name] stock: X".

③ Write stockLabelSafe, which also turns null into "checking", without using a default.

④ Display the Cable and the USB Hub again, this time with stockLabelSafe.

JavaScript / TypeScript Editor

Run code to see output

Collecting Any Number of Arguments — Rest Parameters

Some functions receive a different number of values on every call, like the tags on a product or the line items on an expense report. Even if you declare five parameters, some call will eventually need a sixth, and the unused ones are undefined every time.

A rest parameter (a way of receiving the remaining arguments bundled into a single array; also called variadic arguments) is written as ...labels at the end of the parameter list. It's the same ... that collected the remaining elements in destructuring, except that here it collects arguments. It can only be the last parameter; putting another parameter after it is a SyntaxError.

// Writing ...labels bundles the arguments received into a single array
const buildTags = (...labels) => `Tags (${labels.length}): ${labels.join(" / ")}`;

console.log(buildTags("Free Shipping", "In Stock"));  // Tags (2): Free Shipping / In Stock
console.log(buildTags("New"));                        // Tags (1): New

// Going the other way, ... inside the call's parentheses turns an array back into arguments
console.log(buildTags(...["New", "Limited"]));        // Tags (2): New / Limited

// When there are also named parameters, put the rest parameter last
const buildNotice = (orderId, ...notes) => `${orderId} (${notes.join(", ")})`;

console.log(buildNotice("A-1021", "Chilled", "Delivery window"));  // A-1021 (Chilled, Delivery window)
console.log(buildNotice("A-1022"));                                // A-1022 ()
Which Parameter Each Value Goes Into
1st"A-1021"Goes intoorderId2nd"Chilled"Goes intonotes[0]3rd"Delivery window"Goes intonotes[1]
The first value goes into orderId, and the rest go into the elements of notes, in order. A rest parameter receives whatever's left after the named parameters.

A rest parameter can't have a default, but when no arguments are passed for it, notes is an empty array, not undefined. Going the other way, if you have an array and want to pass its items as individual arguments, write ... inside the call's parentheses.

Total up the transportation costs from a business trip. trainFare, busFare, and taxiFare are already declared.

① Write sumExpenses with a rest parameter so it adds up every amount it receives and returns the total.

② Display the total of the 3 fares as "Total: X yen".

③ Display the result of calling it with no arguments, in the same form.

④ Write summarize, which takes a category name followed by any number of amounts, then pass it "Transportation" and the 3 fares and display "Transportation: X yen (N items)".

JavaScript / TypeScript Editor

Run code to see output

Passing Settings by Name — Options Objects and Destructuring

When settings pile up, as with sending an email, you end up with calls like sendMail(to, subject, false, true). You can't tell what that false and true mean without opening the function's definition.

With the options pattern (a way of passing several settings bundled into a single object), the receiving side destructures the object and gives each key a default. To handle calls that pass no settings at all, add = {} after the destructuring pattern so an empty object is used when no object is passed.

// The more parameters there are, the harder it is to tell what a call is passing
const sendMailOld = (to, subject, isUrgent, withPdf) =>
  `${to} / ${subject} / ${isUrgent} / ${withPdf}`;

console.log(sendMailOld("alice@example.com", "Shipping notice", false, true));
// alice@example.com / Shipping notice / false / true

// options pattern: bundle the settings into one object and destructure it on the receiving side
const sendMail = (to, { subject = "Notice", isUrgent = false } = {}) =>
  `${to} / ${subject} / ${isUrgent}`;

console.log(sendMail("alice@example.com", { isUrgent: true }));  // alice@example.com / Notice / true
console.log(sendMail("alice@example.com"));                     // alice@example.com / Notice / false
Only the Keys You Write Get Overridden
Pass{ isUrgent: true }true reachesisUrgentisUrgent istrueNo subject inthe same callsubject'sdefault appliessubject isNoticeAnother call,no 2nd argument= {} fills inempty settingsBoth usedefaults
Only the keys you write get passed; the ones you leave out keep their defaults. The = {} at the end handles calls that leave out the whole object.

Instead of depending on parameter order, each value is labeled by its key, so calls stay readable even as settings grow. Keys are matched by name, so even if you write them in a different order, as in { isUrgent: true, subject: "Resend" }, the values received are the same.

Leaving Out = {} Causes a TypeError

If you write only the destructuring pattern, as in { subject = "Notice" }, a call that leaves out the second argument throws TypeError: Cannot read properties of undefined (reading 'subject'). That's because it tries to read a key from undefined, which is what the left-out parameter holds.

Switch how a boxed lunch's price is displayed, using a settings object. lunchPrice is already declared.

① Write formatPrice, which takes a pre-tax price and a settings object and returns a string like "1058 yen". Destructure the settings, with a default tax rate of 0.08 and a default unit of "yen".

② Display the result of calling it without settings.

③ Display the result with a tax rate of 0.1 and the unit "yen (dine-in)".

④ Display the result with a tax rate of 0.

JavaScript / TypeScript Editor

Run code to see output
QUIZ

Knowledge Check

Answer each question one by one.

Q1With const addFee = (price, fee = 500) => price + fee;, what does addFee(1000, undefined) return?

Q2What does const countNotes = (id, ...notes) => notes.length; return when called as countNotes("A-1021")?

Q3What does const send = (to, { urgent = false } = {}) => urgent; return when called as send("alice@example.com")?