Skip to content
Plurafy

Optional Chaining and Nullish Coalescing

Safe property access with optional chaining (?.) and nullish coalescing (??)

You’re working with nested API responses that may have missing properties at any level. How do you safely access deeply nested values without multiple null checks?

Optional chaining allows safe property access when properties might not exist. Nullish coalescing provides defaults only for null/undefined, unlike the || operator which treats falsy values as missing.

// ❌ Old approach - multiple checks
const user = apiResponse?.data?.user;
if (user !== null && user !== undefined) {
const email = user.email;
}
// ✅ Optional chaining - safe access
const email = apiResponse?.data?.user?.email;
console.log(email); // undefined if any level is null/undefined
// ✅ Nullish coalescing - provide default
const displayName = user.name ?? 'Anonymous';
// Uses default only if name is null/undefined, not if it's empty string or 0
// Compare with ||
const width = size || 100; // Uses default if size is 0, '', false
const width2 = size ?? 100; // Uses default only if size is null/undefined
const user = {
name: 'Alice',
address: {
street: '123 Main St',
city: 'NYC'
},
getEmail: () => 'alice@example.com'
};
// Property access
console.log(user?.name); // 'Alice'
console.log(user?.phone); // undefined
// Nested property
console.log(user?.address?.zipCode); // undefined
// Method calling
console.log(user?.getEmail?.()); // 'alice@example.com'
console.log(user?.getPhone?.()); // undefined (method doesn't exist)
// Array indexing
const items = ['a', 'b'];
console.log(items?.[0]); // 'a'
console.log(items?.[10]); // undefined
// API response handling
async function processUserData(apiResponse) {
// Safe nested access
const userEmail = apiResponse?.data?.user?.email;
const phoneNumber = apiResponse?.data?.user?.phone?.number;
const preferences = apiResponse?.data?.user?.settings?.notifications ?? true;
// Safe method calls
const userId = apiResponse?.data?.getUser?.().id;
return {
email: userEmail ?? 'no-email',
phone: phoneNumber ?? 'no-phone',
preferences
};
}
// DOM manipulation
function setButtonState(buttonId, disabled) {
const button = document.getElementById(buttonId);
button?.addEventListener?.('click', handleClick);
button?.classList?.toggle?.('disabled', disabled);
button?.setAttribute?.('disabled', disabled);
}
// Conditional logic with coalescing
const config = {
retries: 0,
timeout: null
};
const maxRetries = config.retries ?? 3; // Uses 3 (0 is falsy but not nullish)
const timeoutMs = config.timeout ?? 5000; // Uses 5000 (null is nullish)
const obj = {
a: null,
b: { c: 'value' }
};
// Short-circuits at first null/undefined
console.log(obj.a?.b?.c); // undefined (stops at obj.a which is null)
// Won't cause error even if property doesn't exist
const result = obj?.x?.y?.z; // undefined (doesn't throw)
// Useful in error cases
function handleApiError(error) {
const errorMessage = error?.response?.data?.message ?? 'Unknown error';
const statusCode = error?.response?.status ?? 500;
console.log(`[${statusCode}] ${errorMessage}`);
}
// Optional chaining + logical AND
const permissions = user?.permissions;
if (permissions && permissions.includes('admin')) {
// Safe: permissions is checked before using includes()
}
// Nullish coalescing + logical OR
const theme = localStorage.getItem('theme') ??
(prefersDark ? 'dark' : 'light');
// Multiple null coalesce
const port = process.env.PORT ??
process.env.DEFAULT_PORT ??
3000;
// Optional chaining doesn't call getters on the way
const obj = {
get prop() {
console.log('getter called');
return undefined;
},
nested: { value: 42 }
};
console.log(obj?.prop?.nested); // 'getter called' THEN undefined
// Getter is called even though result is undefined
// Nullish coalescing vs logical operators
const x = false ?? 'default'; // false (false is not nullish)
const y = false || 'default'; // 'default' (false is falsy)
const a = 0 ?? 100; // 0 (0 is not nullish)
const b = 0 || 100; // 100 (0 is falsy)
const s = '' ?? 'empty'; // '' (empty string is not nullish)
const t = '' || 'empty'; // 'empty' (empty string is falsy)