In JavaScript, what is the difference between null and undefined, and why does a default parameter value apply when an argument is undefined but not when it is null?
answer
- who produced the value
- one happens to you, one is chosen
- the default has exactly one trigger
- JSON payloads walk past your defaults
- destructuring follows the same rule
basics
~20 sundefined is the absence the language itself produces: unassigned variables, missing arguments, missing properties, functions with no return. null is an absence a programmer assigns deliberately. Default parameter values fire only for undefined, never for null.
solid answer
~50 sBoth are primitives meaning "no value", but they come from different places. `undefined` is what the runtime hands you when it has nothing: a declared-but-unassigned variable, an omitted argument, a property that was never set, a function that ends without `return`. `null` is never produced by ordinary evaluation — someone wrote it to say "intentionally empty". The default-parameter rule follows directly from that split: a default initialiser runs when the argument's value is `undefined`, whether it was omitted or passed explicitly as `undefined`. `null` is a real value the caller chose, so the language takes it at face value and the default is skipped — `greet(null)` with `function greet(name = 'guest')` gives you `null`, not `'guest'`. Destructuring defaults follow the same rule, which is why a JSON payload full of `null` blows straight past every default you wrote.
code
javascript · 11 linesfunction greet(name = 'guest') {
return `hi ${name}`;
}
console.log(greet()); // hi guest
console.log(greet(undefined)); // hi guest
console.log(greet(null)); // hi null
console.log(greet('')); // hi
const { port = 8080 } = { port: null };
console.log(port); // nullgo deeper
Know that undefined is what JavaScript gives you when nothing was supplied and null is something a programmer writes, and that a default parameter value does not apply when null is passed.
State the exact trigger — the argument's value being undefined, whether omitted or passed explicitly — and show that destructuring defaults follow the same rule. Explain that initialisers run per call, not once at definition.
Bring the boundary problem: databases and APIs emit explicit null, which flies past every default and throws downstream. Describe where you normalise and why one boundary conversion beats defensive checks scattered through the code.
Own the absence convention across services and clients: which representation crosses the wire, whether clearing a field is distinguishable from omitting it, and how partial-update semantics are defined. An unstated convention here costs you a class of null-related defects indefinitely.
## Two primitives, two origins `undefined` and `null` are both primitive types with exactly one value each, and both mean "nothing here". The useful distinction is not philosophical, it is about **who produced the value**. `undefined` is the language's own answer when it has nothing to give: ```js let x; // declared, never assigned -> undefined function f(a) { return a; } f(); // omitted argument -> undefined ({}).missing; // property that was never set -> undefined function g() {} g(); // no return statement -> undefined ``` `null` never appears by itself. It appears because someone typed it, or because a library chose it as a signal. A few standard-library calls do: `String.prototype.match` returns `null` when the pattern does not match, and `Object.getPrototypeOf` returns `null` at the end of a prototype chain. The rule of thumb stands: **undefined happens to you, null is chosen**. A small syntactic asymmetry follows from history: `null` is a literal built into the grammar, while `undefined` is an ordinary identifier that resolves to a non-writable global property. You cannot write `null = 1`, but you *can* introduce a local binding named `undefined` in an inner scope and shadow it. ## The default-parameter rule A default initialiser is not "used when the argument is missing" — it is used when **the argument's value is `undefined`**. Those are not the same statement, and the difference is exactly what interviewers probe. ```js function greet(name = 'guest') { return `hi ${name}`; } greet(); // 'hi guest' — omitted greet(undefined); // 'hi guest' — explicitly undefined counts as missing greet(null); // 'hi null' — null is a value, so it is kept greet(''); // 'hi ' — empty string is a value too greet(0); // 'hi 0' ``` The reasoning is consistent with the origins above. `undefined` is what "nothing was supplied" *looks like* in this language, so it is the trigger. `null` is a decision the caller made, and silently overriding a caller's explicit decision would be surprising. Destructuring defaults use the same rule, in object and array patterns alike: ```js const { port = 8080 } = { port: null }; console.log(port); // null, not 8080 const [first = 'a'] = [undefined]; console.log(first); // 'a' ``` ## When the initialiser runs Default initialisers are evaluated **per call**, at call time, and only for the parameters that need them — they are not computed once when the function is defined. That means they can be arbitrary expressions, can call functions, and can refer to parameters declared to their left: ```js function connect(host, port = defaultPortFor(host)) { /* ... */ } ``` `defaultPortFor` runs only on calls that omit `port` (or pass `undefined`), and it sees the `host` for that particular call. ## Where this bites in real code The classic failure is data crossing a boundary that turns "absent" into `null`. Databases store `NULL`; many serialisation formats and APIs emit an explicit `null` for an empty field rather than omitting the key. That value arrives in your function, sails past every default you wrote, and your code goes on to call a method on `null` — which throws. The bug reads as "my defaults do not work", when in fact they worked exactly as specified. The fix is a normalisation decision, made once at the boundary rather than defensively at every call site: either map incoming `null` to `undefined` as data enters the system, or stop relying on parameter defaults for that field and handle the absent case explicitly inside the function. ## Which one should your own code produce? There is no universal right answer, and interviewers are usually listening for a *defended* position rather than a particular one. - **Prefer `undefined` everywhere.** Consistent with what the language itself produces, and parameter and destructuring defaults then work on your own data. - **Prefer `null` for deliberate emptiness.** Gives you a way to distinguish "the caller explicitly cleared this" from "the caller said nothing", which matters for partial-update APIs where clearing a field and leaving it alone are different operations. What is genuinely wrong is having no rule, so that a field is sometimes missing, sometimes `undefined` and sometimes `null`, and every consumer has to test for all three. ## How to say it in an interview "`undefined` is the absence the runtime produces; `null` is the absence a programmer writes. Defaults trigger on `undefined` only — omitted or explicitly passed — because `null` is a value the caller chose. That is why `null` from an API response walks straight past your defaults."
- You control an API that returns a user's middle name. Should an absent middle name be null or an omitted key?Pick one and apply it everywhere. Omitting the key lets consumers' destructuring defaults do the work, since a missing property reads as `undefined`. An explicit `null` is the better choice when you need to distinguish "cleared deliberately" from "not provided" — a partial-update endpoint, for instance. What breaks consumers is emitting sometimes one and sometimes the other.
- How would you make a default apply to null as well, without special-casing every parameter?Normalise at the boundary: as data enters your module, map `null` to `undefined` for the fields where absence and emptiness mean the same thing. Then parameter and destructuring defaults work as written. Scattering per-parameter checks through the function body reintroduces the same bug wherever someone forgets one.
- Is a default parameter expression evaluated once or on every call?On every call that needs it. The initialiser is an expression evaluated at call time, only for parameters whose argument is `undefined`, and it can see parameters declared to its left. That is why `function f(list = [])` gives each call a fresh array, whereas referring to a shared module-level array would give every call the same one.
- Does a property explicitly set to undefined behave the same as a missing property when you destructure it?For defaults, yes — both read as `undefined`, so the default applies in either case. They differ for anything that inspects keys rather than values: the key is present in one object and absent in the other, which enumeration and key-listing operations can see. If your code cares about that distinction, do not rely on reading the value alone.
saying these in an interview costs you the question
- Says null and undefined are interchangeable
- Claims defaults apply whenever the argument is falsy
- Expects a default to replace an explicitly passed null
- Thinks passing undefined explicitly skips the default
- Believes default expressions are evaluated once at definition time