Follow AiTechWorlds on LinkedIn for professional AI content!Follow Now →
12 minLesson 13 of 35
Modern ES6+ JavaScript

Optional Chaining & Nullish Coalescing

Optional Chaining & Nullish Coalescing

These two operators, introduced in ES2020, solve some of the most common sources of crashes in JavaScript code. Optional chaining prevents Cannot read property of undefined errors, and nullish coalescing provides a safe fallback without the quirks of ||.

Optional Chaining (?.)

Without optional chaining, accessing nested properties on potentially null values crashes your app:

const user = null;
user.name;           // TypeError: Cannot read properties of null
user?.name;          // undefined — safe, no crash

// Deep nesting
const order = {
    customer: {
        address: null
    }
};

order.customer.address.city;    // TypeError: Cannot read properties of null
order?.customer?.address?.city; // undefined — safe

Property Access

// Object properties
user?.name
user?.profile?.avatar?.url

// Array elements
const items = null;
items?.[0]          // undefined (not items[0] which crashes)
items?.[0]?.name    // also safe

// Dynamic property access
const key = "name";
user?.[key]         // same as user?.name

Method Calls

// Optional method calls
const element = document.getElementById("missing");
element?.addEventListener("click", handler);  // no error if null
element?.classList?.add("active");

// Function might be undefined
config.onSuccess?.("done");   // calls it only if it's a function
callback?.();                  // call if it exists

// Chaining optional calls
const result = obj?.method?.()?.property;

With Nullish Coalescing

The combo of ?. and ?? is extremely common:

// ?. returns undefined when chain fails
// ?? provides a fallback for null/undefined
const city = user?.address?.city ?? "Unknown";
const port = config?.server?.port ?? 3000;
const theme = user?.preferences?.theme ?? "light";

// vs || which has the falsy-value problem
const volume = settings?.audio?.volume ?? 0;  // correct: 0 stays 0
const volume = settings?.audio?.volume || 0;  // wrong: 0 becomes 0 (but 0 IS a valid volume)

Practical Examples

// API response handling
async function getUsername(userId) {
    const response = await fetch(`/api/users/${userId}`);
    const data = await response.json();
    
    // Data might have unexpected structure
    return data?.user?.profile?.displayName ?? data?.user?.name ?? "Anonymous";
}

// Event handler
function handleClick(event) {
    const value = event?.target?.value?.trim() ?? "";
    if (!value) return;
    processInput(value);
}

// Configuration merging
function getConfig(userConfig) {
    return {
        timeout: userConfig?.network?.timeout ?? 5000,
        retries: userConfig?.network?.retries ?? 3,
        debug: userConfig?.logging?.debug ?? false,
    };
}

// React — accessing props safely
function UserCard({ user }) {
    const avatar = user?.profile?.avatarUrl ?? "/default-avatar.png";
    const displayName = user?.displayName ?? user?.email ?? "Unknown User";
    
    return `<img src="${avatar}" alt="${displayName}" />`;
}

Nullish Coalescing Assignment (??=)

// Set a value only if it's currently null or undefined
let config = {};

config.timeout ??= 5000;   // config.timeout = config.timeout ?? 5000
config.retries ??= 3;

// Compared to:
config.timeout = config.timeout ?? 5000;  // same effect

Logical Assignment Operators (All Three)

// ??= — assign if null/undefined
a ??= b;  // a = a ?? b

// ||= — assign if falsy (0, "", false, null, undefined)
a ||= b;  // a = a || b

// &&= — assign if truthy
a &&= b;  // a = a && b

// Examples
let user = { name: "Alice", preferences: null };

user.preferences ??= {};         // set preferences if null
user.preferences.theme ??= "light"; // set theme if not set

let count = 0;
count ||= 1;  // count becomes 1 (0 is falsy)
// Be careful: this also triggers on 0, "" etc. Use ??= for numbers!

let cached = getFromCache();
cached &&= transform(cached);  // only transform if it exists

When NOT to Use Optional Chaining

Optional chaining can mask bugs. Sometimes you want the crash because it means something unexpected happened:

// If user is always supposed to exist at this point,
// crashing early is better than silently returning undefined
function getUsername(user) {
    return user.name;  // intentional — user must exist
}

// vs a public API where the caller might pass null
function formatName(user) {
    return user?.name ?? "Anonymous";  // defensive
}

Use ?. at system boundaries (API responses, user input, optional config) and trust your internal data structures to be what you expect.

Next lesson: Array Methods Mastery — map, filter, reduce, and every array method you need.

📱

Get this course's notes on Telegram!

Free cheat sheets, summaries & practice exercises

Get Notes Free →
!