skip to content

Parameters, Defaults and Rest

Modern JavaScript gives you default values, rest parameters, and destructured option objects in the signature itself — and leaves the legacy `arguments` object behind as a trap. Interviewers probe this to see whether you know why rest is an array and `arguments` is not, and how defaults interact with undefined vs null.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

questions

6

In JavaScript, when does a default parameter value actually apply, and what happens if the caller passes null?

level: juniorimportance: must knowfreq 75%

answer

  1. one trigger only
  2. missing and explicit are the same
  3. null is a value, not an absence
  4. falsy is not the test
  5. undefined only, never null or zero

basics

~20 s

A default applies only when the argument is undefined — omitted, or explicitly passed as undefined. Every other value, including null, 0, empty string, false and NaN, is used as-is, so null never triggers the default.

solid answer

~40 s

Default parameter values in JavaScript are triggered by exactly one condition: the argument's value is `undefined`. That covers both an omitted argument and an explicitly passed `undefined`, which is why `f()` and `f(undefined)` behave identically. Anything else is passed through untouched, so `null`, `0`, `''`, `false` and `NaN` all suppress the default. This is the key difference from the older `||` idiom — `function f(x) { x = x || 10 }` also replaces `0` and `''` because those are falsy, while `function f(x = 10) {}` only replaces `undefined`. If you genuinely want `null` to fall back too, either use `??` inside the body or normalise the argument yourself; the parameter default alone will not do it.

code

javascript · 9 lines
javascript
function greet(name = 'guest') {
  return `hello ${name}`;
}

console.log(greet());          // hello guest
console.log(greet(undefined)); // hello guest
console.log(greet(null));      // hello null
console.log(greet(0));         // hello 0
console.log(greet(''));        // hello

go deeper

for a junior

Be ready to say plainly that the default applies only when the argument is undefined, and that null, 0 and empty string are passed through unchanged. Naming the || idiom as the buggy older alternative is a strong extra.

for a middle

Explain the mechanics: each parameter is bound, then any parameter still holding undefined has its initialiser evaluated, lazily and per call. Contrast ||, ?? and parameter defaults on the exact value sets each one replaces.

for a senior

Show the production angle — a default that swallows 0 or '' is a silent data bug, and forwarding an unset variable between functions is how explicit undefined reaches a callee in real code. Say how you would make null mean "use the default" when an external API sends it.

for a principal

Own the API-shape decision: positional optionals force callers to pass undefined as a placeholder, which is the point at which you move to an options object. Argue for a single, documented convention across a codebase for how absence is expressed rather than mixing ||, ?? and defaults per function.

## What a default parameter is Since ES2015 a function parameter can carry an initialiser directly in the signature: ```js function greet(name = 'guest') { return `hello ${name}`; } ``` The expression after `=` is the *default value*. It is not applied unconditionally: the runtime binds each parameter to the corresponding argument, and then, for each parameter that has an initialiser, checks whether the bound value is `undefined`. Only in that case is the initialiser evaluated and its result assigned. ## The single trigger: undefined The rule has no exceptions worth remembering beyond the one word: **undefined**. Two situations produce it. 1. The argument is missing. `greet()` passes no argument, so `name` starts out `undefined`. 2. The argument is present but its value is `undefined`. `greet(undefined)` is indistinguishable from `greet()` as far as the default is concerned. Everything else is a real value and is kept: ```js greet(); // 'hello guest' greet(undefined); // 'hello guest' greet(null); // 'hello null' greet(0); // 'hello 0' greet(''); // 'hello ' greet(false); // 'hello false' greet(NaN); // 'hello NaN' ``` That `greet(null)` line is where candidates most often guess wrong. `null` is a deliberate "no value" *value*; the language treats it as data the caller chose to send, not as an absence. ## Why this differs from the `||` idiom Before defaults existed, the common pattern was: ```js function area(width) { width = width || 100; // pre-ES2015 idiom return width * width; } area(0); // 10000 — the zero was thrown away ``` `||` tests *falsiness*, so it swallows `0`, `''`, `false`, `NaN` and `null` along with `undefined`. That is a real bug class: a user-supplied `0` silently becomes the fallback. Parameter defaults test only `undefined`, which is almost always the behaviour you meant. The nullish coalescing operator `??` (ES2020) sits between the two — it falls back for `undefined` *and* `null`, but not for other falsy values: ```js function area(width) { const w = width ?? 100; // 0 survives, null does not return w * w; } ``` So if your API wants `null` to mean "use the default", combine the two: keep the parameter default for the omitted case and normalise `null` in the body, or drop the parameter default and use `??` alone. ## Interaction with positional arguments Because a default only fires on `undefined`, `undefined` becomes the idiomatic "skip this one" placeholder when you want a later positional argument: ```js function slice(source, start = 0, end = source.length) { return source.slice(start, end); } slice('abcdef', undefined, 3); // 'abc' — start falls back to 0 ``` Passing `null` there would give `start === null`, which coerces to `0` in the arithmetic but is a different value entirely and would break a parameter whose default is a non-numeric object. This awkwardness is the usual argument for switching to a destructured options object once a function has more than two or three optional inputs. ## A default is an expression, evaluated per call The initialiser is a full expression, not a literal, and it is re-evaluated on every call that needs it. `function log(msg, at = Date.now()) {}` stamps a fresh timestamp each time, and `function collect(item, into = [])` creates a brand-new array per call, so calls never share state through the default. The expression can also call a function, which gives the standard "required argument" trick: ```js const required = (name) => { throw new TypeError(`${name} is required`); }; function save(record = required('record')) { return record; } save(); // TypeError: record is required save(null); // returns null — null is a value, so nothing throws ``` That last line is the same lesson again: the guard only fires on `undefined`. ## What an interviewer is checking They want to hear the word `undefined` used precisely, the explicit statement that `null` and `0` are preserved, and ideally the contrast with `||` and `??`. Saying "the default kicks in when the argument is missing" is only half right — it misses `f(undefined)`, which is the case that actually shows up in real code when one function forwards an unset variable to another.

  • If you want null to fall back to the default as well, how do you express that?
    The parameter default cannot do it, so normalise inside the body with nullish coalescing: `function f(x) { const v = x ?? 10; }`. `??` treats both `undefined` and `null` as absent while preserving `0`, `''` and `false`. Keeping the parameter default *and* a `??` in the body is redundant — pick one place to define the fallback so readers do not have to reconcile two.
  • Does a default parameter change what arguments.length reports?
    No. `arguments.length` is the count of arguments the caller actually passed, before any default is applied. `function f(a = 1) {}` called as `f()` sees `arguments.length === 0` even though `a` is `1`. That is a reliable way to distinguish "omitted" from "explicitly passed undefined" if an API really needs to, though needing that distinction usually signals the signature should change.
  • Is the default expression evaluated even when the caller supplies a value?
    No. The initialiser is evaluated lazily, only for the calls where the parameter is `undefined`. So `function f(x = expensive())` never runs `expensive()` when a value is passed, and a default like `throw`-ing a required-argument helper is safe to leave in the signature — it only fires on the calls that omit the argument.

saying these in an interview costs you the question

  • Says the default fires for any falsy argument
  • Claims null triggers the default like undefined does
  • Treats a parameter default as equivalent to x = x || fallback
  • Thinks the default only applies when the argument is omitted, not when undefined is passed
  • Assumes the default expression runs on every call regardless

context

open as a page

In JavaScript, what does a ...rest parameter give you that the legacy arguments object does not?

level: middleimportance: must knowfreq 70%

basics

~20 s

A rest parameter binds a real Array, so map, filter and reduce work directly, and it holds only the arguments beyond the named ones. The arguments object is array-like, holds every argument, is not available in arrow functions, and needs conversion before array methods work.

open as a page

In JavaScript, when are default parameter expressions evaluated, and can one default refer to another parameter?

level: middleimportance: should knowfreq 42%

basics

~20 s

Default expressions are evaluated at call time, lazily, left to right, and only for parameters that are undefined. A default may reference parameters to its left; referencing one to its right throws a ReferenceError because that binding is still in its temporal dead zone.

open as a page

In JavaScript, why is `function connect({ retries = 3, timeout = 1000 } = {})` written with the trailing `= {}`, and what call still breaks it?

level: seniorimportance: should knowfreq 45%

basics

~20 s

The trailing = {} lets the function be called with no argument at all: without it, destructuring undefined throws a TypeError. It does not protect against an explicit null, which is not undefined, so the default never applies and destructuring null still throws.

open as a page

In JavaScript, what does spreading an array into a call such as `Math.max(...numbers)` actually do, and why can it fail when the array is very large?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Spread at a call site iterates the value and passes each element as a separate argument. With a very large array that means hundreds of thousands of individual arguments, which exceeds the engine's argument limit and throws a RangeError, so large inputs need chunking or a reduce loop instead.

open as a page

What does a JavaScript function's `length` property report, and how do default and rest parameters change it?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

A function's length is its declared arity: the number of parameters before the first one with a default value and before any rest parameter. Defaults and rest contribute nothing, and a destructuring pattern counts as one parameter.

open as a page