JS Optional Chaining (?.) Syntax Complete Guide
Master JavaScript optional chaining (?.) to safely access deeply nested object properties, call methods, and index arrays without runtime errors.
Accessing deeply nested properties in JavaScript objects is one of the most common sources of runtime errors. A single undefined in the chain causes the entire expression to throw a TypeError. Optional chaining (?.) solves this by short-circuiting to undefined when it hits a nullish value, eliminating the need for verbose manual checks.
Introduced in ES2020, optional chaining has become one of the most widely used modern JavaScript features. This guide covers every form of the operator with practical examples you will use daily.
The Problem Optional Chaining Solves
Consider an API response where some fields may or may not exist:
const user = {
name: "Alex",
address: {
street: "123 Main St",
city: "Portland"
}
};
const anotherUser = {
name: "Jordan"
// no address property at all
};
// This works fine
console.log(user.address.city); // "Portland"
// This crashes
console.log(anotherUser.address.city);
// TypeError: Cannot read properties of undefined (reading 'city')Before optional chaining, you had to write defensive checks:
// Old approach - verbose and hard to read
const city = anotherUser && anotherUser.address && anotherUser.address.city;
// Or with ternary
const city2 = anotherUser.address ? anotherUser.address.city : undefined;Both approaches become unwieldy as nesting increases. Optional chaining replaces all of this with a single operator.
Basic Property Access with ?.
The ?. operator checks if the left side is null or undefined. If it is, the entire expression returns undefined instead of throwing an error:
const user = {
name: "Jordan",
profile: {
bio: "Full-stack developer",
social: {
twitter: "@jordan_dev"
}
}
};
// Safe nested access
console.log(user.profile?.bio); // "Full-stack developer"
console.log(user.profile?.social?.twitter); // "@jordan_dev"
console.log(user.settings?.theme); // undefined (no crash)
console.log(user.settings?.theme?.color); // undefined (no crash)The operator short-circuits the entire chain. Once any link in the chain evaluates to null or undefined, evaluation stops and returns undefined immediately.
Optional Chaining with Method Calls
You can use ?.() to call a method that might not exist. This is especially useful when working with objects that may or may not implement certain interfaces:
const logger = {
info(msg) { console.log(`[INFO] ${msg}`); },
warn(msg) { console.log(`[WARN] ${msg}`); }
};
// info exists, so it runs
logger.info?.("Application started"); // [INFO] Application started
// debug does NOT exist, returns undefined silently
logger.debug?.("Verbose output"); // undefined (no crash)
// Without optional chaining this would throw
// logger.debug("Verbose output"); // TypeError: logger.debug is not a functionCalling Methods on Potentially Null Objects
const users = [
{ name: "Maya", greet() { return `Hi, I'm ${this.name}`; } },
{ name: "Alex" } // no greet method
];
users.forEach(user => {
const greeting = user.greet?.();
console.log(greeting ?? "No greeting available");
});
// Hi, I'm Maya
// No greeting availableOptional Chaining with Bracket Notation
Use ?.[] to safely access properties using bracket notation, which is essential for dynamic keys or keys with special characters:
const translations = {
en: { greeting: "Hello", farewell: "Goodbye" },
es: { greeting: "Hola", farewell: "Adiós" }
};
const lang = "fr";
// Dynamic key that might not exist
console.log(translations[lang]?.greeting); // undefined
console.log(translations["en"]?.greeting); // "Hello"
// Useful with variables
function getTranslation(locale, key) {
return translations[locale]?.[key] ?? "Translation missing";
}
console.log(getTranslation("es", "greeting")); // "Hola"
console.log(getTranslation("fr", "greeting")); // "Translation missing"Optional Chaining with Arrays
You can safely access array elements that might not exist:
const data = {
users: [
{ name: "Alice", scores: [95, 88, 92] },
{ name: "Bob" } // no scores
]
};
// Safe array access
console.log(data.users?.[0]?.name); // "Alice"
console.log(data.users?.[0]?.scores?.[1]); // 88
console.log(data.users?.[1]?.scores?.[0]); // undefined (no crash)
console.log(data.users?.[5]?.name); // undefined (index out of bounds)Combining with Nullish Coalescing (??)
Optional chaining pairs naturally with the nullish coalescing operator (??) to provide default values:
const config = {
display: {
theme: "dark"
// no fontSize property
}
};
// Optional chaining + nullish coalescing
const theme = config.display?.theme ?? "light";
const fontSize = config.display?.fontSize ?? 16;
const sidebar = config.layout?.sidebar ?? true;
console.log(theme); // "dark" (found)
console.log(fontSize); // 16 (default)
console.log(sidebar); // true (default)This combination is strictly better than using || for defaults because ?? only triggers on null or undefined, not on 0, "", or false:
const settings = { volume: 0, name: "" };
// || treats 0 and "" as falsy - WRONG defaults
console.log(settings.volume || 50); // 50 (bug! 0 is valid)
console.log(settings.name || "Anonymous"); // "Anonymous" (bug!)
// ?? only triggers on null/undefined - CORRECT
console.log(settings.volume ?? 50); // 0 (correct!)
console.log(settings.name ?? "Anonymous"); // "" (correct!)All Three Forms Summarized
| Form | Syntax | Use Case |
|---|---|---|
| Property access | obj?.prop | Access a property on a possibly nullish object |
| Bracket access | obj?.[expr] | Dynamic property access with a possibly nullish object |
| Method call | obj?.method() | Call a method that might not exist |
When NOT to Use Optional Chaining
Optional chaining is powerful, but overusing it can mask real bugs:
// BAD - overuse hides bugs
const name = response?.data?.user?.profile?.name?.first;
// If 'first' is missing, is that a bug or expected?
// BETTER - chain only at the uncertainty boundary
const user = response?.data?.user;
if (user) {
// Inside here, profile.name.first should exist
// If it doesn't, a crash here reveals a data contract violation
const firstName = user.profile.name.first;
}Guidelines for When to Use ?.
| Scenario | Use ?. ? | Reasoning |
|---|---|---|
| API response data | Yes | External data is inherently unreliable |
| Optional object properties | Yes | Property may or may not be set by design |
| DOM elements | Yes | querySelector might return null |
| Required function params | No | Missing required data should throw immediately |
| Internal state you control | Rarely | Masking bugs in your own code is dangerous |
| After explicit null checks | No | Already validated, ?. adds noise |
Real-World Example: Safe API Response Parser
Here is a practical pattern for parsing API responses where the shape is not guaranteed:
function parseUserResponse(response) {
const user = response?.data?.user;
return {
id: user?.id ?? null,
name: user?.name ?? "Unknown User",
email: user?.email ?? null,
avatar: user?.profile?.avatar?.url ?? "/default-avatar.png",
role: user?.permissions?.role ?? "viewer",
lastLogin: user?.activity?.lastLogin
? new Date(user.activity.lastLogin)
: null,
tags: user?.metadata?.tags ?? []
};
}
// Works with complete data
const full = parseUserResponse({
data: {
user: {
id: 1,
name: "Maya",
email: "maya@example.com",
profile: { avatar: { url: "/avatars/maya.jpg" } },
permissions: { role: "admin" },
activity: { lastLogin: "2026-03-01" },
metadata: { tags: ["developer", "lead"] }
}
}
});
// Works with partial data
const partial = parseUserResponse({ data: { user: { id: 2, name: "Alex" } } });
// Works with empty response
const empty = parseUserResponse({});
console.log(empty.name); // "Unknown User"
console.log(empty.avatar); // "/default-avatar.png"Common Mistakes to Avoid
Using ?. on the Left Side of Assignment
Optional chaining is a read-only operation. You cannot use it in assignments:
const user = {};
// SyntaxError - cannot use ?. for assignment
// user?.name = "Alex";
// Correct - check first, then assign
if (user) {
user.name = "Alex";
}Confusing ?. with the || Operator
const data = { count: 0, message: "" };
// These are NOT equivalent
data.count?.toString(); // "0" (count exists, just equals 0)
(data.count || 10).toString(); // "10" (0 is falsy, so falls through)Chaining After a ?. that Returned undefined
Any further property access on undefined would normally throw, but ?. handles this:
// Each ?. independently checks for null/undefined
const val = obj?.a?.b?.c;
// Equivalent to:
// obj == null ? undefined : obj.a == null ? undefined : obj.a.b == null ? undefined : obj.a.b.cBest Practices
- Use
?.at the uncertainty boundary, not on every property access. Know which parts of your data might be missing. - Pair with
??for defaults instead of using||which falsely triggers on0,"", andfalse. - Do not use
?.to silence bugs in data you control. If a property must exist, let it throw so you find the problem early. - Use
?.()for optional callbacks in configuration objects, event handlers, and plugin systems. - Keep chains short. If you need more than three
?.operators in a single expression, extract the intermediate value and add a proper null check.
Rune AI
Key Insights
- Use
?.to safely access uncertain data: it returnsundefinedinstead of throwing when a link in the chain is nullish - Pair with
??for clean defaults: the combinationobj?.prop ?? fallbackis the modern pattern for safe data access with defaults - Apply at the uncertainty boundary only: overusing
?.on every property access masks real bugs in your code - Works for properties, brackets, and methods:
?.prop,?.[key], and?.()cover all access patterns - Supported everywhere modern JS runs: ES2020+ with full browser and Node.js support, plus transpiler options for legacy targets
Frequently Asked Questions
Does optional chaining work with delete?
What is the difference between ?. and the logical AND (&&) pattern?
Can I use optional chaining in template literals?
Is optional chaining supported in all browsers?
Does optional chaining affect performance?
Conclusion
Optional chaining (?.) eliminates an entire category of TypeError crashes caused by accessing properties on null or undefined. Its three forms (property access ?.prop, bracket access ?.[], and method call ?.()) cover every situation where data might be missing, from API responses to optional configuration objects.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.