⚡ The Default Value That Only Worked for One Kind of Missing
A default value in a destructuring pattern only kicks in when the corresponding position is exactly undefined – not when it is missing from a shorter array, and specifically not when it is present but holds the value null, which JavaScript treats as a deliberate, distinct value rather than an absence of one. Code that destructures the result of a function which can return null for a field – a database lookup that found no match, or a parsed value that failed to parse – quietly skips every default value for that field, leaving null to flow forward into code that expected either a real value or the configured fallback, and getting neither.
🔎 The Problem
function getUserPrefs(userId) {
// theme comes back as null specifically when the user has no
// saved preference row at all - not undefined, null:
const row = lookupPrefsRow(userId);
return [row ? row.theme : null, row ? row.fontSize : null];
}
const [theme = 'light', fontSize = 14] = getUserPrefs(42);
// Expected: theme === 'light', fontSize === 14, when there's no
// saved row. What actually happens: theme === null, fontSize ===
// null - the default values never apply, because null is not
// undefined, and a destructuring default only triggers for
// undefined specifically.
applyTheme(theme); // called with null, not 'light'
setFontSize(fontSize); // called with null, not 14
✅ Fix: Normalize to Undefined Before the Default Ever Has to Apply
- Convert a deliberate null into undefined at the source – right where the lookup can return null – before the value ever reaches a destructuring pattern, so the default value mechanism sees exactly the signal it actually checks for.
- Use the nullish coalescing operator explicitly on each value instead of relying on a destructuring default when a value can be either null or undefined, since nullish coalescing treats both the same way and a destructuring default does not.
- Add a direct equality check for null immediately after any API, parser, or database call known to return it for a missing value, and decide there – deliberately – whether the rest of the code should treat that null the same as undefined or differently.
⚠️ Why This Is Easy to Miss
- A destructuring default reads as a general catch-all for any kind of missing value, so it is a reasonable, common assumption that it covers null as well as undefined – the actual rule is narrower than the syntax visually suggests.
- A quick manual test calling the function with a completely missing argument, rather than one that explicitly returns null, exercises the undefined case and shows the default working correctly, hiding the gap until a real null value happens to flow through.
A destructuring default only answers to one specific value – null raises its hand just as clearly as any value does, and the default never hears it.
