ESC

Type to search the knowledge base.

Default Parameters

ES6 default parameter values — evaluated at call time, temporal dead zone with lets, and why undefined triggers defaults but null does not.

beginner3 min read
  • javascript
  • default-parameters
  • functions

Default parameters replace the old arg = arg || 'fallback' pattern with something that only kicks in for undefined — not for every falsy value. That single detail fixes bugs with 0 and '' and creates new ones if you pass null expecting a default.

Basics

function greet(name = 'world', punctuation = '!') {
  return `Hello, ${name}${punctuation}`;
}

greet(); // "Hello, world!"
greet('Ada'); // "Hello, Ada!"
greet('Ada', '?'); // "Hello, Ada?"
greet(undefined, '?'); // "Hello, world?"
greet(null); // "Hello, null!" — null does NOT trigger default

Only missing arguments and explicit undefined use the default.

Evaluated at call time

function append(x, list = []) {
  list.push(x);
  return list;
}

append(1); // [1]
append(2); // [2] — fresh array each call

Defaults are re-evaluated per call. You do not share one array across calls (unlike a mutable default in some other languages’ pitfalls — JS got this right).

let id = 0;
function next(n = ++id) {
  return n;
}
next(); // 1
next(); // 2
next(99); // 99 — default expression not run

Defaults can use earlier params

function box(width, height = width) {
  return { width, height };
}
box(10); // { width: 10, height: 10 }

Later params cannot be referenced from earlier defaults (TDZ):

function broken(a = b, b = 1) {
  return [a, b];
}
// broken() → ReferenceError when evaluating a = b

Destructured defaults

function createUser({
  name = 'Anonymous',
  roles = ['user'],
  settings: { theme = 'light' } = {},
} = {}) {
  return { name, roles, theme };
}

createUser();
createUser({ name: 'Ada' });
createUser({ settings: { theme: 'dark' } });

The = {} on the whole parameter lets you call createUser() with no args. Nested = {} protects when settings is missing.

vs || and ??

function f(x) {
  x = x || 10; // 0 becomes 10 — often wrong
}
function g(x) {
  x = x ?? 10; // only null/undefined
}
function h(x = 10) {
  // only undefined (and missing)
}
Input || ?? default param
missing / undefined default default default
null default default keeps null
0 / '' default keeps keeps

arguments and length

function f(a = 1, b = 2) {}
f.length; // 0 — params with defaults don't count toward length from the first default onward

function g(a, b = 2) {}
g.length; // 1

Defaulted parameters also change arguments object mapping in non-simple parameter lists — prefer rest/explicit params in modern code.

Interview answer

“Default parameters apply when the argument is missing or undefined, not null or other falsy values. Default expressions run at call time, so [] as a default is safe. I use them with destructuring for option objects, and I pick ?? when I need null to fall back outside parameter lists.”

Temporal dead zone with defaults

function order(a = b, b = 2) {
  return [a, b];
}
// order() → ReferenceError: Cannot access 'b' before initialization

function ok(a, b = a) {
  return [a, b];
}
ok(1); // [1, 1]

Default expressions see earlier parameters, outer scope, and the function’s own bindings carefully — but not later parameters. Also: reassigning a defaulted parameter does not update arguments[i] in modern non-mapped parameter lists; don’t mix arguments with defaults.

Further reading

Related guides