JavaScript 56 🧬 Optional chaining ?.
const user = {
name: 'Alice',
address: {
city: 'Paris',
zip: '75001'
}
};
console.log(user?.name);
console.log(user?.address?.city);
console.log(user?.address?.zip);
console.log(user?.phone?.number);
const obj = null;
console.log(obj?.name);
const arr = [1, 2, 3];
console.log(arr?.[0]);
console.log(arr?.[10]);
const fn = null;
console.log(fn?.());
const api = {
getName() { return 'Alice'; }
};
console.log(api.getName?.());
console.log(api.getAge?.());
const config = {
port: 0,
host: null,
debug: false
};
console.log(config?.host ?? 'localhost');
console.log(config?.port ?? 8080);
console.log(config?.debug ?? true);
let user1;
console.log(user1?.profile?.name ?? 'Guest');
Optional chaining (?.) lets you safely access deeply nested properties without checking every level. If any link in the chain is null or undefined, the whole expression short-circuits and returns undefined — instead of throwing a TypeError.
Key point: ?. short-circuits the entire expression the moment it hits null or undefined. It doesn’t just skip one step — it stops everything after it, including function calls and array access. This makes it the cleanest way to safely dig into unknown data.
a – What is optional chaining
Optional chaining is a syntax for safely reading nested properties. Instead of writing guard after guard, you chain with ?. and let it fail gracefully.
The problem it solves:
Without ?., accessing a nested property on a null or undefined value throws:
const user = null;
console.log(user.name);
// TypeError: Cannot read properties of null (reading 'name')
The old workaround was verbose:
const name = user && user.address && user.address.city;
With ?.:
const name = user?.address?.city;
If user is null or undefined, the whole expression evaluates to undefined — no error.
The three forms:
| Form | Purpose | Example |
|---|---|---|
obj?.prop | Property access | user?.name |
obj?.[key] | Dynamic property | user?.[key] |
func?.() | Function call | fn?.() |
All three short-circuit on null or undefined.
Property access (?.):
const user = {
name: 'Alice',
address: {
city: 'Paris'
}
};
console.log(user?.name);
// [ 'Alice' ]
console.log(user?.address?.city);
// [ 'Paris' ]
console.log(user?.phone?.number);
// [ undefined ]
user?.phone is undefined, so ?.number short-circuits and returns undefined.
Dynamic property access (?.[]):
const obj = { key: 'value' };
const key = 'key';
console.log(obj?.[key]);
// [ 'value' ]
const empty = null;
console.log(empty?.[key]);
// [ undefined ]
Use ?.[] when the key comes from a variable.
Function call (?.()):
const api = {
getName() { return 'Alice'; }
};
console.log(api.getName?.());
// [ 'Alice' ]
console.log(api.getAge?.());
// [ undefined ]
If the method exists, it’s called. If not, the whole expression is undefined.
Chaining:
const city = user?.address?.city?.toUpperCase();
Every step is protected. If any is null or undefined, the result is undefined.
Short-circuit behavior:
Once ?. hits null or undefined, everything after it is skipped:
const user = null;
console.log(user?.address.city);
// [ undefined ] ← address.city is never evaluated
Even though .city doesn’t have a ?., it’s never reached because user?. short-circuited.
This applies to function calls too:
const obj = null;
console.log(obj?.method());
// [ undefined ] ← method() is never called
Why ?. matters:
- Concise — no more
&&chains - Safe — no more TypeErrors from nested access
- Readable — intent is clear
- Composable — works with
??,[], and calls - Standard — part of ES2020, supported everywhere modern
Optional chaining vs &&:
// Old way
const city = user && user.address && user.address.city;
// New way
const city = user?.address?.city;
The ?. version is shorter, clearer, and less error-prone.
?. vs ?. — same operator, different contexts:
The operator is ?. — a dot preceded by a question mark. Where you place it determines the form:
obj?.prop // property
obj?.[key] // dynamic property
obj?.method() // method call
Warning: Don’t write
? .with a space — that’s a syntax error. And don’t confuse?.with the ternary? :.
b – Short-circuiting and common patterns
The short-circuit behavior is what makes ?. powerful. Understanding it unlocks cleaner code.
The rule: The moment ?. sees null or undefined, it returns undefined and stops evaluating.
Whole chain stops:
const user = null;
console.log(user?.address?.city?.zip?.plus4);
// [ undefined ]
Only the first ?. matters here — the rest never run.
Function calls don’t happen:
let calls = 0;
const obj = {
method() {
calls++;
return 'called';
}
};
const nullObj = null;
console.log(nullObj?.method());
// [ undefined ]
console.log(calls);
// [ 0 ] ← method never ran
Array indices:
const arr = [1, 2, 3];
console.log(arr?.[0]);
// [ 1 ]
const nullArr = null;
console.log(nullArr?.[0]);
// [ undefined ]
Combining with ??:
The two operators are natural partners — ?. for safe access, ?? for defaults:
const user = {
profile: null
};
const city = user.profile?.address?.city ?? 'Unknown';
console.log(city);
// [ 'Unknown' ]
Pattern 1 — Safe nested access:
function getStreet(user) {
return user?.address?.street ?? 'No address';
}
Pattern 2 — Optional methods:
element?.addEventListener?.('click', handler);
Calls the method only if it exists.
Pattern 3 — Safe callbacks:
function run(callback) {
callback?.();
}
run(); // no error
run(() => console.log('called')); // called
Pattern 4 — Optional config:
function setup(config) {
const port = config?.port ?? 8080;
const host = config?.host ?? 'localhost';
return { port, host };
}
Pattern 5 — Event handlers:
onSuccess?.(result);
onError?.(error);
Common in libraries where hooks are optional.
Pattern 6 — JSON or API responses:
const name = response?.data?.user?.name ?? 'Anonymous';
Pattern 7 — Array of objects:
const users = [{ name: 'Alice' }, null, { name: 'Bob' }];
const names = users.map(u => u?.name ?? 'Unknown');
// [ [ 'Alice', 'Unknown', 'Bob' ] ]
Pattern 8 — Cleanup handlers:
const cleanup = () => console.log('cleanup');
cleanup?.();
Pattern 9 — Chained methods:
const result = value?.toString?.().toUpperCase?.();
Rare but valid — every step is guarded.
Pattern 10 — Options objects:
function render(element, options) {
const width = options?.width ?? 100;
const height = options?.height ?? 100;
// ...
}
What ?. does NOT do:
?. only checks for null and undefined. Other falsy values (0, '', false, NaN) don’t short-circuit:
const obj = { count: 0 };
console.log(obj?.count);
// [ 0 ] ← 0 doesn't short-circuit
console.log(obj?.count?.toString?.());
// [ '0' ]
0 isn’t nullish, so the chain continues.
Common mistake — no short-circuit fallback:
const value = obj?.a?.b ?? 'default';
This uses ?? to catch the undefined that ?. returns. Without ??, you’d just get undefined.
Comparison table:
| Expression | When obj is null | When obj is {a: 1} |
|---|---|---|
obj.a | TypeError | 1 |
obj?.a | undefined | 1 |
obj?.a?.b | undefined | undefined |
obj?.method() | undefined | call result |
obj?.[k] | undefined | obj[k] |
Short-circuit gotcha — ?. doesn’t protect assignments:
const obj = null;
obj?.prop = 'value';
// TypeError: Cannot set properties of null
?. works for reads, not writes. To conditionally assign:
if (obj != null) obj.prop = 'value';
delete with ?.:
const obj = null;
delete obj?.prop;
// [ true ] ← delete is a no-op, no error
delete short-circuits safely.
Optional chaining is not a null check replacement:
// ❌ Not equivalent
if (obj?.prop) { ... } // false if prop is 0, '', false
// ✅ Better
if (obj?.prop != null) { ... }
?. returns undefined on short-circuit — but a value like 0 still fails a truthiness check.
c – Real-world uses of optional chaining
Where ?. earns its keep in production code.
Pattern 1 — API response validation:
function getUsername(response) {
return response?.data?.user?.username ?? 'guest';
}
Pattern 2 — Config with defaults:
function configure(options = {}) {
return {
host: options?.host ?? '0.0.0.0',
port: options?.port ?? 3000,
debug: options?.debug ?? false
};
}
Pattern 3 — Optional callbacks in components:
class Component {
constructor(props) {
this.onMount = props?.onMount;
this.onUnmount = props?.onUnmount;
}
mount() {
this.onMount?.();
}
unmount() {
this.onUnmount?.();
}
}
Pattern 4 — Safe method calls on DOM:
const el = document.querySelector('.missing');
el?.addEventListener('click', handler);
No error if the element isn’t found.
Pattern 5 — Tree traversal:
function getValue(tree, path) {
let current = tree;
for (const key of path) {
current = current?.[key];
}
return current;
}
const tree = { a: { b: { c: 42 } } };
console.log(getValue(tree, ['a', 'b', 'c']));
// [ 42 ]
console.log(getValue(tree, ['a', 'x', 'c']));
// [ undefined ]
Pattern 6 — Optional constructor arguments:
function createUser(input) {
return {
name: input?.name ?? 'Anonymous',
email: input?.email ?? null,
age: input?.age ?? 0
};
}
Pattern 7 — Iterating optional arrays:
const items = data?.items ?? [];
items.forEach(item => console.log(item));
If data.items is missing, items becomes an empty array — no crash.
Pattern 8 — Optional event fields:
function handleClick(event) {
const x = event?.clientX ?? 0;
const y = event?.clientY ?? 0;
console.log(`clicked at ${x}, ${y}`);
}
Pattern 9 — Nested map/object lookups:
const lookup = {
users: {
alice: { id: 1, name: 'Alice' }
}
};
function getUser(username) {
return lookup?.users?.[username] ?? null;
}
console.log(getUser('alice'));
// [ { id: 1, name: 'Alice' } ]
console.log(getUser('bob'));
// [ null ]
Pattern 10 — Version strings:
const version = process?.versions?.node ?? 'unknown';
Useful in environments where process might not exist.
Pattern 11 — Optional chaining in JSX (React):
function Profile({ user }) {
return <h1>{user?.name ?? 'Guest'}</h1>;
}
Pattern 12 — Safe array access:
const first = arr?.[0];
const last = arr?.[arr.length - 1];
Pattern 13 — Access on optional promise results:
const result = await fetchData?.();
const name = result?.user?.name;
Pattern 14 — Optional modules:
const config = require('config')?.default ?? {};
Pattern 15 — Safe property chains on unknown data:
function extractCity(order) {
return order?.shipping?.address?.city ?? 'N/A';
}
Pattern 16 — Optional Map lookups:
const map = new Map();
const value = map?.get?.('key') ?? 'default';
Pattern 17 — Optional chaining with destructuring:
const { name = 'Anonymous' } = user?.profile ?? {};
Pattern 18 — Optional array of options:
const first = options?.items?.[0]?.value ?? 'default';
Pattern 19 — Cascading fallbacks:
function getTheme(user) {
return user?.preferences?.theme
?? user?.settings?.theme
?? 'light';
}
Pattern 20 — Full example:
function renderUserCard(data) {
const name = data?.user?.name ?? 'Anonymous';
const avatar = data?.user?.avatar ?? '/default.png';
const city = data?.user?.address?.city ?? 'Unknown';
const bio = data?.user?.bio ?? '';
return { name, avatar, city, bio };
}
console.log(renderUserCard({}));
// [ { name: 'Anonymous', avatar: '/default.png', city: 'Unknown', bio: '' } ]
What ?. replaces:
// Before
const name = user && user.profile && user.profile.name;
// After
const name = user?.profile?.name;
What ?. enables with ??:
const name = user?.profile?.name ?? 'Guest';
When to use ?.:
| Situation | Use |
|---|---|
| API responses | ✅ |
| Optional config | ✅ |
| Optional callbacks | ✅ |
| Known-safe objects | ⚠️ Not needed |
| Hot loops | ⚠️ Minor overhead |
When NOT to use ?.:
| Situation | Why |
|---|---|
| You expect a value | Fail fast is better |
| Real bugs | ?. hides them |
| Missing required data | Throw a clear error |
| Deeply nested access | Refactor instead |
?. can hide bugs:
// ❌ Silent failure
const total = order?.items?.reduce(...) ?? 0;
// If `order` is required but missing, you get 0 instead of an error
If a value should exist, let it throw. Use ?. only when the absence is expected.
Complete Example Session
// ============================================
// PART 1: BASIC PROPERTY ACCESS
// ============================================
const user = {
name: 'Alice',
address: { city: 'Paris', zip: '75001' }
};
console.log(user?.name);
// [ 'Alice' ]
console.log(user?.address?.city);
// [ 'Paris' ]
console.log(user?.phone?.number);
// [ undefined ]
// ============================================
// PART 2: NULL ROOT
// ============================================
const obj = null;
console.log(obj?.name);
// [ undefined ]
// ============================================
// PART 3: ARRAY ACCESS
// ============================================
const arr = [1, 2, 3];
console.log(arr?.[0]);
// [ 1 ]
console.log(arr?.[10]);
// [ undefined ]
const nullArr = null;
console.log(nullArr?.[0]);
// [ undefined ]
// ============================================
// PART 4: FUNCTION CALL
// ============================================
const api = {
getName() { return 'Alice'; }
};
console.log(api.getName?.());
// [ 'Alice' ]
console.log(api.getAge?.());
// [ undefined ]
// ============================================
// PART 5: NULL FUNCTION
// ============================================
const fn = null;
console.log(fn?.());
// [ undefined ]
// ============================================
// PART 6: SHORT-CIRCUIT
// ============================================
const nullUser = null;
console.log(nullUser?.address?.city?.zip);
// [ undefined ]
// ============================================
// PART 7: CALL NOT MADE
// ============================================
let calls = 0;
const obj2 = {
method() { calls++; return 'called'; }
};
const nullObj = null;
console.log(nullObj?.method());
// [ undefined ]
console.log(calls);
// [ 0 ]
// ============================================
// PART 8: WITH NULLISH COALESCING
// ============================================
const config = { port: 0, host: null };
console.log(config?.host ?? 'localhost');
// [ 'localhost' ]
console.log(config?.port ?? 8080);
// [ 0 ]
// ============================================
// PART 9: DYNAMIC KEY
// ============================================
const obj3 = { key: 'value' };
const key = 'key';
console.log(obj3?.[key]);
// [ 'value' ]
// ============================================
// PART 10: SAFE CALLBACK
// ============================================
function run(cb) {
cb?.();
}
run();
console.log('after');
// [ 'after' ]
run(() => console.log('called'));
// [ 'called' ]
// ============================================
// PART 11: NESTED CHAIN
// ============================================
let user1;
console.log(user1?.profile?.name ?? 'Guest');
// [ 'Guest' ]
// ============================================
// PART 12: METHOD NOT PRESENT
// ============================================
const mod = {};
console.log(mod.method?.());
// [ undefined ]
// ============================================
// PART 13: ARRAY OF OBJECTS
// ============================================
const users = [{ name: 'Alice' }, null, { name: 'Bob' }];
console.log(users.map(u => u?.name ?? 'Unknown'));
// [ [ 'Alice', 'Unknown', 'Bob' ] ]
// ============================================
// PART 14: NOT SHORT-CIRCUITED BY FALSY
// ============================================
const data = { count: 0 };
console.log(data?.count);
// [ 0 ]
console.log(data?.count?.toString?.());
// [ '0' ]
// ============================================
// PART 15: ASSIGNMENT FAILS
// ============================================
try {
const nullObj2 = null;
nullObj2?.prop = 'value';
} catch (err) {
console.log(err.message);
}
// [ Cannot set properties of null (setting 'prop') ]
// ============================================
// PART 16: DELETE SAFE
// ============================================
const nullObj3 = null;
console.log(delete nullObj3?.prop);
// [ true ]
// ============================================
// PART 17: OPTIONAL MODULE
// ============================================
const config3 = { port: 3000 };
const settings = config3?.default ?? config3;
console.log(settings.port);
// [ 3000 ]
// ============================================
// PART 18: TREE WALK
// ============================================
function getValue(tree, path) {
let current = tree;
for (const key of path) {
current = current?.[key];
}
return current;
}
const tree = { a: { b: { c: 42 } } };
console.log(getValue(tree, ['a', 'b', 'c']));
// [ 42 ]
console.log(getValue(tree, ['a', 'x', 'c']));
// [ undefined ]
// ============================================
// PART 19: DESTRUCTURING WITH DEFAULTS
// ============================================
const { name = 'Anonymous' } = user?.profile ?? {};
console.log(name);
// [ 'Anonymous' ]
// ============================================
// PART 20: FULL SCRIPT
// ============================================
const user56 = {
name: 'Alice',
address: { city: 'Paris', zip: '75001' }
};
console.log(user56?.name);
console.log(user56?.address?.city);
console.log(user56?.address?.zip);
console.log(user56?.phone?.number);
const obj56 = null;
console.log(obj56?.name);
const arr56 = [1, 2, 3];
console.log(arr56?.[0]);
console.log(arr56?.[10]);
const fn56 = null;
console.log(fn56?.());
const api56 = {
getName() { return 'Alice'; }
};
console.log(api56.getName?.());
console.log(api56.getAge?.());
const config56 = { port: 0, host: null, debug: false };
console.log(config56?.host ?? 'localhost');
console.log(config56?.port ?? 8080);
console.log(config56?.debug ?? true);
let user1_56;
console.log(user1_56?.profile?.name ?? 'Guest');
Quick Reference
The Three Forms
| Form | Use for | Example |
|---|---|---|
?. | Property | user?.name |
?.[] | Dynamic key | obj?.[key] |
?.() | Function call | fn?.() |
Short-Circuit Rules
| Rule | Effect |
|---|---|
| Null root | Returns undefined |
| Undefined root | Returns undefined |
| Falsy root (0, ”, false) | Continues chain |
| Whole chain stops | a?.b.c.d never reaches c or d |
| Function not called | obj?.fn() skips call |
vs Alternatives
| Pattern | Old way | New way |
|---|---|---|
| Property | a && a.b | a?.b |
| Call | fn && fn() | fn?.() |
| Array | a && a[i] | a?.[i] |
| Default | a && a.b || c | a?.b ?? c |
?. + ??
| Expression | Result |
|---|---|
a?.b ?? 'x' | 'x' if missing |
a?.b?.c ?? 'x' | 'x' if any missing |
fn?.() ?? 'x' | 'x' if fn missing |
Works With
| Feature | Supported |
|---|---|
| Object properties | ✅ |
| Array indices | ✅ |
| Function calls | ✅ |
| Optional methods | ✅ |
delete | ✅ |
| Destructuring | ✅ |
| Assignments | ❌ |
Does Not Short-Circuit On
| Value | ?. behavior |
|---|---|
0 | Continues |
'' | Continues |
false | Continues |
NaN | Continues |
null | Short-circuits |
undefined | Short-circuits |
Gotchas
| Issue | Solution |
|---|---|
obj?.prop = x | Doesn’t protect assignments |
if (obj?.prop) | Falsy values break truthiness |
a?.[key] | Use bracket for dynamic key |
a?. b | Space is syntax error |
a ?. b : c | Not the ternary — space matters |
Best Practices
✅ Do This:
// Safe nested access
const city = user?.address?.city; // ✅
// With fallback
const name = user?.profile?.name ?? 'Guest'; // ✅
// Optional callbacks
callback?.(); // ✅
// Optional methods
obj.method?.(); // ✅
// Dynamic keys
obj?.[key]; // ✅
// Array access
arr?.[0]; // ✅
// API responses
data?.items?.forEach(...); // ✅
// Deleting optional props
delete obj?.prop; // ✅
❌ Don’t Do This:
// Don't overuse — hides bugs
const name = user?.profile?.name; // ⚠️ if user is required
// Don't rely on truthiness after ?.
if (obj?.count) { ... } // ❌ fails for 0
// Don't expect assignments to be protected
obj?.prop = 'x'; // ❌ TypeError if null
// Don't chain with a space
obj ?. prop; // ❌ SyntaxError
// Don't confuse with ternary
obj ?.method() : other; // ❌ wrong syntax
// Don't chain for deeply nested known data
const x = a?.b?.c?.d?.e; // ⚠️ refactor instead
// Don't ignore undefined
const val = obj?.value; // ✅
console.log(val + 1); // ❌ NaN if undefined
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
Space before ?. | SyntaxError | No space |
Assignment with ?. | TypeError | Check null first |
Truthiness after ?. | Falsy values fail | Use != null |
| Confusing with ternary | Syntax error | ?. is one operator |
| Overusing | Hides bugs | Use only when absent is OK |
Not adding ?? | Just undefined | Add fallback |
0 and '' | Not short-circuited | Expected |
| Deep chains | Performance cost | Refactor |
Real-World Examples
1. Basic Property
const user = { name: 'Alice' };
console.log(user?.name);
// [ 'Alice' ]
2. Null Root
const user = null;
console.log(user?.name);
// [ undefined ]
3. Nested Access
const user = { address: { city: 'Paris' } };
console.log(user?.address?.city);
// [ 'Paris' ]
4. Missing Intermediate
const user = {};
console.log(user?.address?.city);
// [ undefined ]
5. Array Index
const arr = [1, 2, 3];
console.log(arr?.[0]);
// [ 1 ]
6. Missing Array
const arr = null;
console.log(arr?.[0]);
// [ undefined ]
7. Dynamic Key
const obj = { key: 'value' };
const key = 'key';
console.log(obj?.[key]);
// [ 'value' ]
8. Function Call
const fn = () => 'called';
console.log(fn?.());
// [ 'called' ]
9. Null Function
const fn = null;
console.log(fn?.());
// [ undefined ]
10. Optional Method
const api = { getName: () => 'Alice' };
console.log(api.getName?.());
// [ 'Alice' ]
11. Missing Method
const api = {};
console.log(api.getName?.());
// [ undefined ]
12. With Nullish Coalescing
const user = null;
console.log(user?.name ?? 'Guest');
// [ 'Guest' ]
13. Optional Callback
function run(cb) { cb?.(); }
run();
// (no output)
run(() => console.log('called'));
// [ 'called' ]
14. Short-Circuit
const user = null;
console.log(user?.a?.b?.c);
// [ undefined ]
15. Falsy Continues
const obj = { count: 0 };
console.log(obj?.count);
// [ 0 ]
16. With Optional Chaining
const city = user?.address?.city ?? 'Unknown';
console.log(city);
// [ 'Unknown' ]
17. Array of Objects
const users = [{ name: 'Alice' }, null];
console.log(users.map(u => u?.name ?? 'Unknown'));
// [ [ 'Alice', 'Unknown' ] ]
18. Tree Walk
function get(tree, path) {
let cur = tree;
for (const k of path) cur = cur?.[k];
return cur;
}
console.log(get({ a: { b: 42 } }, ['a', 'b']));
// [ 42 ]
19. Destructuring
const { name = 'Anonymous' } = user?.profile ?? {};
console.log(name);
// [ 'Anonymous' ]
20. Full Script
const user56 = {
name: 'Alice',
address: { city: 'Paris', zip: '75001' }
};
console.log(user56?.name);
console.log(user56?.address?.city);
console.log(user56?.address?.zip);
console.log(user56?.phone?.number);
const obj56 = null;
console.log(obj56?.name);
const arr56 = [1, 2, 3];
console.log(arr56?.[0]);
console.log(arr56?.[10]);
const fn56 = null;
console.log(fn56?.());
const api56 = {
getName() { return 'Alice'; }
};
console.log(api56.getName?.());
console.log(api56.getAge?.());
const config56 = { port: 0, host: null, debug: false };
console.log(config56?.host ?? 'localhost');
console.log(config56?.port ?? 8080);
console.log(config56?.debug ?? true);
let user1_56;
console.log(user1_56?.profile?.name ?? 'Guest');
Visual: Short-Circuit Behavior
┌──────────────────────────────────────────────┐
│ user?.address?.city │
│ │
│ user = null │
│ │ │
│ └──► user? → undefined │
│ │ │
│ └──► STOP │
│ address never read │
│ city never read │
│ │
│ Result: undefined │
│ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ user = { address: null } │
│ │
│ user? → user (not nullish) │
│ │ │
│ └──► address? → undefined │
│ │ │
│ └──► STOP │
│ city never read │
│ │
│ Result: undefined │
│ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ user = { address: { city: 'Paris' } } │
│ │
│ user? → user │
│ address? → address │
│ city → 'Paris' │
│ │
│ Result: 'Paris' │
│ │
└──────────────────────────────────────────────┘
Visual: ?. vs &&
┌──────────────────────────────────────────────┐
│ Old way (&&) │
│ │
│ user && user.address && user.address.city │
│ │
│ Verbose. Repeats the path. │
│ Returns the falsy value, not undefined. │
│ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ New way (?.) │
│ │
│ user?.address?.city │
│ │
│ Concise. No repetition. │
│ Always returns undefined on short-circuit. │
│ │
└──────────────────────────────────────────────┘
Visual: Three Forms
┌──────────────────────────────────────────────┐
│ Property: obj?.prop │
│ │
│ Dynamic: obj?.[key] │
│ │
│ Call: fn?.() │
│ │
│ Same operator — different context │
│ │
└──────────────────────────────────────────────┘
Summary
| Concept | Syntax | Example |
|---|---|---|
| Property | obj?.prop | user?.name |
| Dynamic | obj?.[key] | obj?.[key] |
| Call | fn?.() | cb?.() |
| Chain | a?.b?.c | Any level safe |
| With default | a?.b ?? 'x' | Fallback |
| Short-circuit | Stops at first null/undefined | — |
| Falsy continues | 0, '', false | Not nullish |
| Assignment | ❌ not protected | obj?.p = x fails |
delete | ✅ protected | delete obj?.p |
Compose with ?? | ✅ | a?.b ?? c |
Key takeaways:
?.safely accesses properties, indices, and method calls- It short-circuits on
nullorundefined— returnsundefinedand skips the rest - Three forms:
obj?.prop,obj?.[key],fn?.() - Only triggers on
nullandundefined— not on0,'', orfalse - Does not protect assignments — only reads
delete obj?.propis safe- Combine with
??for defaults - Use it for API responses, optional config, optional callbacks, and optional methods
- Don’t overuse — if a value should exist, fail fast
- Truthiness checks after
?.still fail for falsy values — use!= null ?.isn’t a substitute for validation — it hides bugs when overused- Works with destructuring, method chaining, and array access
Remember: ?. is the safe navigation operator. Use it to gracefully handle data that might be missing — API responses, optional config, user input. It short-circuits on null and undefined — never on falsy values. Pair it with ?? for clean defaults. Don’t overuse it — sometimes an error is better than silence. Master ?., and nested access stops throwing.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!