Skip to content
JavaScript js functions 4 min read

Default Parameters

Default parameters let you assign a fallback value to a function parameter, used whenever the caller omits that argument or passes undefined. Introduced in ES6 (ES2015), they replace clunky manual checks like x = x || defaultValue with a clean, declarative syntax that lives right in the function signature. Beyond being concise, they sidestep a whole class of subtle bugs around falsy values and make a function’s expected inputs self-documenting.

Basic syntax

You declare a default by following a parameter name with = and an expression. If the argument is missing or explicitly undefined, the expression is evaluated and its result becomes the parameter’s value.

function greet(name = "friend") {
  return `Hello, ${name}!`;
}

console.log(greet("Ada"));
console.log(greet());
console.log(greet(undefined));

Output:

Hello, Ada!
Hello, friend!
Hello, friend!

Notice that passing undefined explicitly triggers the default — it is treated identically to omitting the argument. This is what makes defaults compose cleanly with optional values that may legitimately be undefined.

Only undefined triggers the default

A common point of confusion: null, 0, "", false, and NaN are all valid values and will not trigger the default. Only undefined does.

function withTimeout(ms = 3000) {
  return ms;
}

console.log(withTimeout());      // 3000  (missing → default)
console.log(withTimeout(null));  // null  (passed → kept)
console.log(withTimeout(0));     // 0     (passed → kept)

Output:

3000
null
0

This precision is exactly why default parameters beat the old || trick (covered below).

Defaults are evaluated at call time

Default expressions are not computed once when the function is defined — they run fresh on every call where the argument is missing. This means you can use function calls, object literals, or any expression as a default, and each call gets its own value.

function pushTo(value, arr = []) {
  arr.push(value);
  return arr;
}

console.log(pushTo(1));
console.log(pushTo(2));

Output:

[ 1 ]
[ 2 ]

Each call without arr receives a brand-new array, so there is no shared-state surprise. The same applies to calling a function as a default — handy for timestamps or generated IDs:

function logEvent(message, time = Date.now()) {
  return `[${time}] ${message}`;
}

Because the default expression only runs when needed, an expensive default (like a database lookup) is skipped entirely when the caller supplies the argument.

Referencing earlier parameters

A default expression can reference parameters declared to its left in the same signature. They are initialized left to right, so earlier parameters are already in scope.

function makeRange(start, end = start + 10, step = (end - start) / 5) {
  return { start, end, step };
}

console.log(makeRange(0));
console.log(makeRange(0, 100));

Output:

{ start: 0, end: 10, step: 2 }
{ start: 0, end: 100, step: 20 }

Referencing a parameter that appears later (to the right) throws a ReferenceError, because it is still in its temporal dead zone when the earlier default is evaluated.

Replacing the old || pattern

Before ES6, defaults were faked with logical OR:

function connect(options) {
  options = options || {};
  var port = options.port || 8080;
  // ...
}

The flaw: || returns the right-hand side for any falsy value. If a caller legitimately wants port to be 0, the || 8080 silently overrides it. Default parameters (and the related ?? nullish coalescing operator) only fall back on undefined/null, preserving valid falsy inputs.

ApproachFalls back onFalsy-value safePer-call evaluation
x = x || defevery falsy valueNon/a
x = x ?? defnull and undefinedYesn/a
Default parameterundefined onlyYesYes

For destructured options objects, you can combine both techniques to get parameter-level and property-level defaults at once:

function createServer({ host = "localhost", port = 8080, secure = false } = {}) {
  return `${secure ? "https" : "http"}://${host}:${port}`;
}

console.log(createServer());
console.log(createServer({ port: 0, secure: true }));

Output:

http://localhost:8080
https://localhost:0

The trailing = {} on the whole parameter ensures createServer() with no argument still works, and port: 0 is faithfully respected.

Effect on length and arguments

Defaulted (and rest) parameters are excluded from a function’s length property, which reports only the parameters before the first default.

function f(a, b, c = 1) {}
console.log(f.length); // 2

Also note that in functions using default parameters, arguments no longer stays in sync with named parameters even in non-strict mode — the function body behaves as if it were in strict mode for that aliasing.

Best Practices

  • Reach for default parameters instead of || whenever 0, "", or false could be valid inputs.
  • Put parameters with defaults at the end of the signature so callers can omit them naturally.
  • Use = {} on a destructured options parameter so the function can be called with no arguments.
  • Prefer [] or {} literals as defaults to give each call fresh, unshared state.
  • Keep default expressions cheap and side-effect-free where possible; they run on every defaulted call.
  • Pass undefined (not null) when you want to deliberately fall through to a default.
  • Reach for ?? instead of || when you need a fallback inside the function body rather than the signature.
Last updated June 1, 2026
Was this helpful?