Use ?? when only null and undefined should trigger a default. The || operator also replaces valid falsy values such as zero, false and an empty string.
Reproduce in an empty temporary directory
This isolated example creates only fixture data in its working directory or runs local JavaScript. Run it separately from your project so the named files cannot overwrite real work.
const assert = require('node:assert/strict');
const input = {retries: 0, enabled: false, label: ''};
const kept = {
retries: input.retries ?? 3,
enabled: input.enabled ?? true,
label: input.label ?? 'untitled'
};
assert.deepEqual(kept, input);
assert.equal(input.retries || 3, 3);
assert.equal(null ?? 3, 3);
console.log(JSON.stringify(kept));Observed local output
{"retries":0,"enabled":false,"label":""}What to compare
The retained values can represent meaningful choices: no retries, a disabled option and a deliberately blank label. A broad truthiness default can erase those choices without throwing an exception. Conversely, preserving an empty string is wrong if the application definition says blank is invalid. Choosing the operator should follow the field contract rather than a desire to shorten an if statement.
Choose the next action
List accepted values for each field. Apply a missing-value default and then validate type and range separately. If blank text must use a fallback, test that condition explicitly and decide whether whitespace-only text counts as blank. If zero is disallowed for a quantity, report or correct it under the quantity policy; do not hide an invalid value behind a generic default. Test null, undefined, zero, false and empty text as distinct cases.
Limits and verification
Nullish coalescing is not optional chaining: an unsafe property access can fail before ?? runs. It also cannot be mixed directly with && or || without the required grouping. The fixture demonstrates values already available in an object and does not fetch or validate a remote configuration. Schema validation is still needed when the field could contain a string where a number is expected.
Related: Array Map with parseInt Produces NaN: Stop Passing the Index as the Radix.
Isolated example executed on macOS on November 10, 2026 (KST), using Bash 3.2, Python 3 for the byte fixture, and Node v26.5.0. Results describe the example, not your application or every runtime version.