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 be0,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.lengthstops 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 || fallbackpattern for defaulting when0,"", orfalseare valid inputs — use a default parameter or??instead.
