Topics

Jump to a topic.

Search questions

Find a question by title, tag, or summary.

How do default and rest parameters work?

Questions in this chapter (7)
  • Draft
  • medium
  • Askedsometimes
  • Verified October 2026

Answer

Defaults. function f(a = expr) evaluates expr each time f is called with a missing or undefined. Not at definition time, and not for null, 0, or ''. So list = [] gives every call a fresh array, and a default can call a function with side effects.

Order and scope. Parameters are initialized left to right. A default can read earlier parameters; reading a later one is a TDZ ReferenceError. When any parameter has an expression, FunctionDeclarationInstantiation gives the parameters their own Environment Record and puts body vars in a separate one, so closures in the parameter list can't see declarations in the body.

Rest. ...rest must be last, has no default, and gets the remaining arguments as a real Array. It replaces most uses of arguments.

Side effects on the function.

  • length counts only the parameters before the first default or rest parameter.
  • A parameter list with defaults, rest, or destructuring is "non-simple": a "use strict" directive in the body becomes a SyntaxError, and arguments is the unmapped kind.

Example

let calls = 0;
const nextId = () => ++calls;

function create(id = nextId(), label = `item-${id}`, ...tags) {
  return { id, label, tags };
}

console.log(create()); // → { id: 1, label: 'item-1', tags: [] }
console.log(create(7, undefined, 'a', 'b')); // → { id: 7, label: 'item-7', tags: [ 'a', 'b' ] }
console.log(create(null).id, calls); // → null 1

try {
  ((a = b, b = 1) => {})();
} catch (e) {
  console.log(e.name); // → ReferenceError (b is in its TDZ)
}

const x = 'outer';
function scoped(read = () => x) {
  var x = 'body';
  return read();
}
console.log(scoped()); // → outer

console.log(((a, b = 1, c) => {}).length, ((...r) => {}).length); // → 1 0

Pitfalls

  • Expecting null to trigger a default. Only undefined does; create(null) keeps null.
  • Reading a later parameter in a default: it is in its TDZ. Body-level var and function declarations aren't visible to defaults at all, so a default can't call a helper declared inside the body.
  • Using fn.length to count expected arguments. A default or rest parameter stops the count, which breaks arity-based code like currying.
  • Mutating a shared default. Because the default is re-evaluated, = [] is safe; = sharedArray declared outside is not.

Follow-ups

Is function f(a, b = 1, c) legal?
Yes. c still has no default, so it is undefined when omitted, and it isn't "optional" in any special way. length is 1, because counting stops at the first default.
Why can't a function with a default have "use strict" in its body?
The spec makes it an early SyntaxError for any non-simple parameter list (V8: "Illegal 'use strict' directive in function with non-simple parameter list"). Put the directive in an enclosing function or module instead.

Exercises

Predict the output

function f(a, read = () => a) {
  var a = 2;
  return [a, read()];
}
console.log(f(1));

Predict the output

let calls = 0;
const tick = () => ++calls;
function g(n = tick()) { return n; }
g(); g(undefined); g(0); g(null);
console.log(calls);