| |

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:

FormPurposeExample
obj?.propProperty accessuser?.name
obj?.[key]Dynamic propertyuser?.[key]
func?.()Function callfn?.()

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:

ExpressionWhen obj is nullWhen obj is {a: 1}
obj.aTypeError1
obj?.aundefined1
obj?.a?.bundefinedundefined
obj?.method()undefinedcall result
obj?.[k]undefinedobj[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 ?.:

SituationUse
API responses
Optional config
Optional callbacks
Known-safe objects⚠️ Not needed
Hot loops⚠️ Minor overhead

When NOT to use ?.:

SituationWhy
You expect a valueFail fast is better
Real bugs?. hides them
Missing required dataThrow a clear error
Deeply nested accessRefactor 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

FormUse forExample
?.Propertyuser?.name
?.[]Dynamic keyobj?.[key]
?.()Function callfn?.()

Short-Circuit Rules

RuleEffect
Null rootReturns undefined
Undefined rootReturns undefined
Falsy root (0, ”, false)Continues chain
Whole chain stopsa?.b.c.d never reaches c or d
Function not calledobj?.fn() skips call

vs Alternatives

PatternOld wayNew way
Propertya && a.ba?.b
Callfn && fn()fn?.()
Arraya && a[i]a?.[i]
Defaulta && a.b || ca?.b ?? c

?. + ??

ExpressionResult
a?.b ?? 'x''x' if missing
a?.b?.c ?? 'x''x' if any missing
fn?.() ?? 'x''x' if fn missing

Works With

FeatureSupported
Object properties
Array indices
Function calls
Optional methods
delete
Destructuring
Assignments

Does Not Short-Circuit On

Value?. behavior
0Continues
''Continues
falseContinues
NaNContinues
nullShort-circuits
undefinedShort-circuits

Gotchas

IssueSolution
obj?.prop = xDoesn’t protect assignments
if (obj?.prop)Falsy values break truthiness
a?.[key]Use bracket for dynamic key
a?. bSpace is syntax error
a ?. b : cNot 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

PitfallProblemSolution
Space before ?.SyntaxErrorNo space
Assignment with ?.TypeErrorCheck null first
Truthiness after ?.Falsy values failUse != null
Confusing with ternarySyntax error?. is one operator
OverusingHides bugsUse only when absent is OK
Not adding ??Just undefinedAdd fallback
0 and ''Not short-circuitedExpected
Deep chainsPerformance costRefactor

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

ConceptSyntaxExample
Propertyobj?.propuser?.name
Dynamicobj?.[key]obj?.[key]
Callfn?.()cb?.()
Chaina?.b?.cAny level safe
With defaulta?.b ?? 'x'Fallback
Short-circuitStops at first null/undefined
Falsy continues0, '', falseNot nullish
Assignment❌ not protectedobj?.p = x fails
delete✅ protecteddelete obj?.p
Compose with ??a?.b ?? c

Key takeaways:

  • ?. safely accesses properties, indices, and method calls
  • It short-circuits on null or undefined — returns undefined and skips the rest
  • Three forms: obj?.prop, obj?.[key], fn?.()
  • Only triggers on null and undefined — not on 0, '', or false
  • Does not protect assignments — only reads
  • delete obj?.prop is 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!