- Published on
Today I Learnt: JavaScript Optional Chaining & Nullish Coalescing
- Authors

- Name
- Mehdi Akiki
Reference
Today I Learnt: JavaScript Optional Chaining & Nullish Coalescing
JavaScript's optional chaining (?.) and nullish coalescing (??) operators are modern features that make code safer and more readable by elegantly handling null and undefined values.
Optional Chaining (?.)
The optional chaining operator allows you to access nested properties without worrying about intermediate null or undefined values.
Basic Usage
// Before (error-prone)
const userName = user && user.profile && user.profile.name;
// After (safe and clean)
const userName = user?.profile?.name;
Different Access Patterns
const user = {
profile: {
name: "John",
address: {
street: "123 Main St",
city: null,
},
},
};
// Property access
console.log(user?.profile?.name); // "John"
console.log(user?.profile?.age); // undefined
// Array access
const users = [{ name: "John" }, { name: "Jane" }];
console.log(users?.[0]?.name); // "John"
console.log(users?.[5]?.name); // undefined
// Function calls
const userObj = {
getName: () => "John",
getAge: null,
};
console.log(userObj?.getName?.()); // "John"
console.log(userObj?.getAge?.()); // undefined
Nullish Coalescing (??)
The nullish coalescing operator provides a way to set default values, but only for null or undefined (not falsy values like 0, "", false).
Basic Usage
// Traditional approach (catches all falsy values)
const result1 = value || "default"; // Catches: null, undefined, "", 0, false
// Nullish coalescing (only null/undefined)
const result2 = value ?? "default"; // Only catches: null, undefined
Practical Examples
const config = {
timeout: 0, // Valid value (0 is not null/undefined)
retries: null, // Should use default
enabled: false, // Valid value (false is not null/undefined)
apiKey: undefined, // Should use default
};
const timeout = config.timeout ?? 5000; // 0 (not 5000)
const retries = config.retries ?? 3; // 3
const enabled = config.enabled ?? true; // false (not true)
const apiKey = config.apiKey ?? "default"; // "default"
Combining Both Operators
// Safe property access with defaults
const user = {
profile: null,
};
const displayName = user?.profile?.name ?? "Anonymous";
console.log(displayName); // "Anonymous"
// Complex nested access
const config = {
database: {
connection: {
host: null,
port: 5432,
},
},
};
const host = config?.database?.connection?.host ?? "localhost";
const port = config?.database?.connection?.port ?? 3000;
console.log(host, port); // "localhost", 5432
Real-World Use Cases
API Response Handling
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
return {
id: data?.id ?? null,
name: data?.name ?? "Unknown",
email: data?.email ?? null,
profile: {
avatar: data?.profile?.avatar ?? "/default-avatar.png",
bio: data?.profile?.bio ?? "No bio available",
},
};
} catch (error) {
console.error("Failed to fetch user:", error);
return null;
}
}
Configuration Objects
const defaultConfig = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3,
debug: false,
};
function createConfig(userConfig = {}) {
return {
apiUrl: userConfig?.apiUrl ?? defaultConfig.apiUrl,
timeout: userConfig?.timeout ?? defaultConfig.timeout,
retries: userConfig?.retries ?? defaultConfig.retries,
debug: userConfig?.debug ?? defaultConfig.debug,
};
}
// Usage
const config1 = createConfig(); // All defaults
const config2 = createConfig({ timeout: 10000 }); // Override timeout only
const config3 = createConfig({ apiUrl: null }); // apiUrl will be default
Form Data Processing
function processFormData(formData) {
return {
name: formData?.name?.trim() ?? "",
email: formData?.email?.toLowerCase() ?? "",
age: formData?.age ?? null,
preferences: {
theme: formData?.preferences?.theme ?? "light",
notifications: formData?.preferences?.notifications ?? true,
language: formData?.preferences?.language ?? "en",
},
};
}
// Safe form processing
const formResult = processFormData({
name: " John Doe ",
email: "[email protected]",
preferences: { theme: "dark" },
});
// Result: { name: "John Doe", email: "[email protected]", age: null, preferences: { theme: "dark", notifications: true, language: "en" } }
DOM Element Access
// Safe DOM manipulation
function updateUI(data) {
const titleElement = document.querySelector?.("#title");
const contentElement = document.querySelector?.("#content");
titleElement?.textContent = data?.title ?? "Untitled";
contentElement?.innerHTML = data?.content ?? "<p>No content</p>";
// Safe event listener
const button = document.querySelector?.("#submit-btn");
button?.addEventListener?.("click", handleSubmit);
}
Advanced Patterns
Optional Chaining with Functions
const utils = {
format: {
date: (date) => date?.toLocaleDateString(),
currency: (amount) => amount?.toFixed(2),
},
};
// Safe function calls
const formattedDate = utils?.format?.date?.(new Date()) ?? "No date";
const formattedPrice = utils?.format?.currency?.(29.99) ?? "$0.00";
Nullish Coalescing with Logical Operators
// Combining with logical operators
const result = (data?.value ?? 0) + (config?.multiplier ?? 1);
// Safe array operations
const firstItem = array?.[0] ?? defaultValue;
const arrayLength = array?.length ?? 0;
Error Handling
// Safe error handling
function safeParseJSON(jsonString) {
try {
return JSON.parse(jsonString) ?? {};
} catch {
return {};
}
}
// Safe async operations
async function safeFetch(url) {
try {
const response = await fetch(url);
const data = (await response?.json?.()) ?? null;
return data;
} catch {
return null;
}
}
Best Practices
- Use
?.for Property Access: Replace verbose null checks - Use
??for Defaults: Only when you want to ignore falsy values - Combine Wisely:
?.and??work great together - Consider Performance: Optional chaining has minimal performance impact
- Browser Support: Ensure your target browsers support these features
- TypeScript: These operators work seamlessly with TypeScript
Migration from Old Patterns
// Old way
function getUserName(user) {
if (user && user.profile && user.profile.name) {
return user.profile.name;
}
return "Anonymous";
}
// New way
function getUserName(user) {
return user?.profile?.name ?? "Anonymous";
}
Optional chaining and nullish coalescing are modern JavaScript features that make your code more readable, safer, and less error-prone. They eliminate the need for verbose null checks and provide elegant ways to handle default values.