JavaScript Default Parameters

Default parameters let you give a function parameter a fallback value that is used automatically when the caller omits an argument (or explicitly passes undefined). Before ES6, developers had to manually check for missing arguments inside the function body; default parameters move that logic into the function signature itself, making code shorter, clearer, and less error-prone.

Overview: How Default Parameters Work

In JavaScript, every parameter is optional at the call site — if you call a function with fewer arguments than it declares, the missing parameters are simply set to undefined. Default parameters, introduced in ES6 (2015), let you specify what value a parameter should take instead of undefined when no argument (or the value undefined) is supplied for it.

The key rule to internalize is this: a default value is used only when the argument is missing or explicitly undefined. Any other value — including null, 0, false, or an empty string — is treated as a real value and the default is ignored. This is different from many other “fallback value” techniques you might have seen (like the || operator), which we’ll cover in the Common Mistakes section below.

Under the hood, default parameters are evaluated lazily and freshly on every single call. The default expression is not computed once when the function is defined; it’s computed each time the function runs and only for the parameters that actually need it. This means a default value can be any expression — a literal, a function call, or even a reference to an earlier parameter in the same parameter list — and it will be re-evaluated every time it’s needed.

Parameters with defaults also live in their own special scope, separate from the function body. This parameter scope is evaluated left to right, so a default expression can refer to parameters declared before it, but not to ones declared after it (doing so throws a ReferenceError at call time, because that later parameter hasn’t been initialized yet). It can also reference outer (module- or global-scope) variables normally.

Effect on function.length

One subtle side effect: the built-in function.length property (which normally reports how many parameters a function declares) only counts the parameters before the first one with a default value. So function f(a, b = 1, c) {} has f.length === 1, not 3. This rarely matters in everyday code, but it can surprise you if you rely on .length for argument-count checks or currying utilities.

Syntax

function functionName(param1 = defaultValue1, param2 = defaultValue2) {
  // function body
}
  • param1, param2 — ordinary parameter names.
  • = defaultValue — any valid JavaScript expression (literal, variable, function call, another parameter, etc.) evaluated when the argument is missing or undefined.
  • Default parameters can appear anywhere in the list, but it’s conventional (and clearer) to put parameters without defaults first.
  • Works with regular functions, arrow functions, class methods, and object destructuring patterns used as parameters.
Argument passed Default used?
No argument at all Yes
undefined Yes
null No — null is used as-is
0, "", false, NaN No — falsy but still a real value

Examples

Example 1: A Simple Greeting Function

function greet(name = "Guest") {
  console.log(`Hello, ${name}!`);
}

greet();
greet("Ava");

Output:

Hello, Guest!
Hello, Ava!

When greet() is called with no argument, name is undefined, so JavaScript substitutes the default value "Guest". When an argument is provided, the default is never evaluated at all.

Example 2: A Default That References an Earlier Parameter

function createRectangle(width, height = width) {
  return { width, height, area: width * height };
}

console.log(createRectangle(5));
console.log(createRectangle(5, 3));

Output:

{ width: 5, height: 5, area: 25 }
{ width: 5, height: 3, area: 15 }

Here height‘s default value is the expression width, which refers to the parameter declared just before it. When height is omitted, the rectangle becomes a square using width for both sides. This works because parameter defaults are evaluated left to right in their own scope.

Example 3: Defaults With Destructured Options Objects

function connect({ host = "localhost", port = 8080 } = {}) {
  console.log(`Connecting to ${host}:${port}`);
}

connect();
connect({ port: 3000 });

Output:

Connecting to localhost:8080
Connecting to localhost:3000

This is one of the most common real-world patterns: a single “options object” parameter with its own destructured defaults, plus an outer default of {} in case the caller passes nothing at all. Without that outer = {}, calling connect() with zero arguments would try to destructure undefined and throw a TypeError.

Under the Hood: Evaluation Order and Timing

Only undefined triggers the default

function multiply(a, b = 2) {
  return a * b;
}

console.log(multiply(5));
console.log(multiply(5, undefined));
console.log(multiply(5, null));
console.log(multiply(5, 0));

Output:

10
10
0
0

Both a missing argument and an explicit undefined trigger the default (b becomes 2). But null and 0 are real values, so the engine uses them directly — 5 * null coerces to 0, and 5 * 0 is 0. This distinction trips up a lot of developers, so keep it in mind whenever a caller might legitimately want to pass a falsy value.

Defaults are re-evaluated on every call

let counter = 0;

function nextId(id = ++counter) {
  return id;
}

console.log(nextId());
console.log(nextId());
console.log(nextId(100));
console.log(nextId());

Output:

1
2
100
3

Each call that omits id re-runs the expression ++counter from scratch. When 100 is passed explicitly, the default expression is skipped entirely, so counter is left untouched. This proves defaults aren’t computed once at function-definition time — they’re computed fresh, per call, only when actually needed. (This is a key difference from languages like Python, where a mutable default is evaluated once and shared across calls; JavaScript has no such trap.)

Common Mistakes

Mistake 1: Using || instead of a default parameter

function setVolume(vol) {
  vol = vol || 50;
  console.log(vol);
}

setVolume(0);
setVolume(25);

Output:

50
25

This looks reasonable, but it’s wrong: because 0 is falsy, vol || 50 silently overrides an intentional volume of 0 with 50. A real default parameter only kicks in for undefined, so it handles this case correctly:

function setVolume(vol = 50) {
  console.log(vol);
}

setVolume(0);
setVolume(undefined);

Output:

0
50

If you need a fallback that also treats null as “missing” but keeps other falsy values intact, combine a default parameter with the nullish coalescing operator (??) inside the body rather than reaching for ||.

Mistake 2: Trying to skip a parameter with a blank comma

You cannot leave a positional gap in a function call by writing something like createUser(, "admin") — that’s not valid JavaScript syntax:

createUser(, "admin"); // SyntaxError: Unexpected token ','

To use a parameter’s default while still supplying a later argument, you must pass undefined explicitly in that position:

function createUser(name, role = "member") {
  console.log(`${name ?? "Anonymous"} - ${role}`);
}

createUser(undefined, "admin");
createUser("Sam");

Output:

Anonymous - admin
Sam - member

This is also a strong argument for the destructured-options-object pattern shown in Example 3: it lets callers supply any subset of named values without worrying about positional order at all.

Best Practices

  • Put parameters without defaults before parameters with defaults, unless there’s a clear API reason not to (e.g. an options object last).
  • Prefer default parameters over manual if (x === undefined) x = ... checks inside the function body — they document the contract right in the signature.
  • Don’t use || for defaulting numeric, boolean, or string values that could legitimately be 0, false, or ""; use a default parameter or ?? instead.
  • For functions that take many optional settings, use a destructured object parameter with per-property defaults and an outer = {} fallback.
  • Keep default expressions simple and side-effect-free when possible; save complex logic (like the counter example) for cases where the behavior is intentional and well documented.
  • Remember that function.length stops counting at the first default parameter — don’t rely on it for argument validation once defaults are involved.

Practice Exercises

Exercise 1: Write a function power(base, exponent = 2) that returns base raised to exponent. Call it once with only base and once with both arguments, and log both results.

Exercise 2: Write a function formatPrice(amount, currency = "USD", decimals = 2) that logs the amount fixed to decimals places followed by the currency code (e.g. "19.99 USD"). Make sure passing 0 for decimals actually works (no rounding to 2 places).

Exercise 3: Write a function createTeam({ name, members = [] } = {}) that logs the team name and how many members it has. Call it with no argument, with only a name, and with both name and members, and predict the output before running it.

Summary

  • Default parameters supply a fallback value used only when an argument is missing or explicitly undefined.
  • null, 0, false, and other falsy-but-real values do not trigger the default.
  • Default expressions are evaluated lazily, fresh on every call, and can reference earlier parameters (but not later ones).
  • Default parameters affect function.length, which only counts parameters before the first default.
  • Combine default parameters with destructuring to build clean, self-documenting APIs for optional configuration objects.
  • Avoid the value || fallback pattern for defaulting when 0, "", or false are valid inputs — use a default parameter or ?? instead.