Optional chaining (?.) safely accesses nested properties without explicit null checks. Here's how to use it.
Basic Syntax#
1// Without optional chaining
2const street = user && user.address && user.address.street;
3
4// With optional chaining
5const street = user?.address?.street;
6
7// Returns undefined if any part is null/undefined
8const user = null;
9console.log(user?.name); // undefined (no error)
10
11const user2 = { name: 'Alice' };
12console.log(user2?.name); // 'Alice'
13
14// Works with nested properties
15const data = {
16 user: {
17 profile: {
18 email: 'alice@example.com'
19 }
20 }
21};
22
23console.log(data?.user?.profile?.email); // 'alice@example.com'
24console.log(data?.user?.settings?.theme); // undefinedProperty Access#
1// Object properties
2const obj = { a: { b: { c: 1 } } };
3
4obj?.a?.b?.c; // 1
5obj?.x?.y?.z; // undefined
6
7// Array elements
8const arr = [1, 2, 3];
9arr?.[0]; // 1
10arr?.[10]; // undefined
11
12const matrix = [[1, 2], [3, 4]];
13matrix?.[0]?.[1]; // 2
14matrix?.[5]?.[0]; // undefined
15
16// Dynamic property access
17const key = 'name';
18const user = { name: 'Alice' };
19user?.[key]; // 'Alice'
20
21// Computed keys
22const users = {
23 'user-1': { name: 'Alice' },
24 'user-2': { name: 'Bob' }
25};
26
27const id = 'user-1';
28users?.[id]?.name; // 'Alice'Method Calls#
1// Call method if it exists
2const obj = {
3 greet() {
4 return 'Hello!';
5 }
6};
7
8obj.greet?.(); // 'Hello!'
9obj.farewell?.(); // undefined (no error)
10
11// With arguments
12const calculator = {
13 add(a, b) {
14 return a + b;
15 }
16};
17
18calculator.add?.(1, 2); // 3
19calculator.multiply?.(2, 3); // undefined
20
21// Callback patterns
22function fetchData(callback) {
23 const data = { result: 'success' };
24 callback?.(data); // Only calls if callback exists
25}
26
27fetchData(data => console.log(data)); // { result: 'success' }
28fetchData(); // No error
29
30// DOM methods
31document.querySelector('.button')?.addEventListener('click', handler);With Nullish Coalescing#
1// Combine with ?? for defaults
2const theme = user?.settings?.theme ?? 'light';
3
4// Different from || which catches all falsy values
5const count = data?.count ?? 0;
6// Uses 0 only if count is null/undefined, not if count is 0
7
8// Complex defaults
9const config = {
10 api: {
11 timeout: 0 // Valid value of 0
12 }
13};
14
15// Wrong: treats 0 as falsy
16const timeout1 = config?.api?.timeout || 5000; // 5000
17
18// Correct: only defaults on null/undefined
19const timeout2 = config?.api?.timeout ?? 5000; // 0
20
21// Chained with fallback
22const value = obj?.prop1?.prop2 ?? obj?.fallback ?? 'default';Short-Circuiting#
1// Evaluation stops at first null/undefined
2let callCount = 0;
3function getValue() {
4 callCount++;
5 return { data: 'value' };
6}
7
8const result = null?.prop?.(getValue());
9console.log(callCount); // 0 - getValue never called
10
11// Useful for expensive operations
12const cached = cache?.get(expensiveKey)?.value;
13// If cache is null, expensiveKey isn't evaluated
14
15// Side effects are skipped
16let logged = false;
17const obj = null;
18obj?.method?.(logged = true);
19console.log(logged); // falseCommon Patterns#
1// API response handling
2async function fetchUser(id) {
3 const response = await fetch(`/api/users/${id}`);
4 const data = await response.json();
5
6 return {
7 name: data?.user?.name ?? 'Unknown',
8 email: data?.user?.email ?? '',
9 avatar: data?.user?.profile?.avatar ?? '/default-avatar.png',
10 role: data?.user?.permissions?.role ?? 'guest',
11 };
12}
13
14// Event handling
15function handleEvent(event) {
16 const target = event?.target;
17 const value = target?.value ?? '';
18 const dataset = target?.dataset?.id;
19
20 // Process...
21}
22
23// Configuration merging
24function getConfig(options) {
25 return {
26 host: options?.host ?? 'localhost',
27 port: options?.port ?? 3000,
28 ssl: options?.security?.ssl ?? false,
29 timeout: options?.network?.timeout ?? 5000,
30 };
31}
32
33getConfig(); // Uses all defaults
34getConfig({ port: 8080 }); // Overrides port only
35
36// React component props
37function UserCard({ user }) {
38 return (
39 <div>
40 <h2>{user?.name ?? 'Anonymous'}</h2>
41 <p>{user?.bio?.short ?? 'No bio available'}</p>
42 <img src={user?.avatar?.url ?? '/placeholder.png'} />
43 </div>
44 );
45}Array Methods#
1// Safe array operations
2const users = null;
3
4// Without optional chaining
5const names1 = users ? users.map(u => u.name) : [];
6
7// With optional chaining
8const names2 = users?.map(u => u.name) ?? [];
9
10// First element
11const first = arr?.[0];
12
13// Last element
14const last = arr?.[arr?.length - 1];
15
16// Find with optional chaining
17const found = users?.find(u => u.id === id)?.name;
18
19// Filter and map
20const active = users
21 ?.filter(u => u?.active)
22 ?.map(u => u?.email)
23 ?? [];
24
25// Reduce with fallback
26const total = items
27 ?.reduce((sum, item) => sum + (item?.price ?? 0), 0)
28 ?? 0;With Delete#
1// Delete optional property
2delete obj?.prop; // Safe even if obj is null
3
4// Conditional delete
5const config = {
6 cache: { enabled: true }
7};
8
9delete config?.cache?.enabled; // Works
10delete config?.missing?.prop; // No errorTypeScript Integration#
1// TypeScript understands optional chaining
2interface User {
3 name: string;
4 address?: {
5 street?: string;
6 city: string;
7 };
8}
9
10function getStreet(user: User | null): string | undefined {
11 return user?.address?.street;
12}
13
14// With type narrowing
15function processUser(user: User | null) {
16 const city = user?.address?.city;
17 // city is string | undefined
18
19 if (user?.address) {
20 // user.address is defined here
21 console.log(user.address.city);
22 }
23}
24
25// Non-null assertion (use carefully)
26const definitelyCity = user?.address?.city!;Best Practices#
Usage:
✓ Use for potentially missing properties
✓ Combine with ?? for defaults
✓ Use in API response handling
✓ Safe event handler access
Patterns:
✓ Chain multiple levels safely
✓ Use with array access
✓ Safe method calls
✓ Configuration access
Readability:
✓ Don't overuse on known objects
✓ Consider extracting complex chains
✓ Document expected structures
✓ Use TypeScript for clarity
Avoid:
✗ Using when value is required
✗ Excessive chaining (code smell)
✗ Hiding bugs in data structure
✗ Using || instead of ??
Conclusion#
Optional chaining simplifies safe property access and eliminates verbose null checks. Combine with nullish coalescing (??) for default values. Use it for API responses, configuration objects, and any situation where properties may be undefined. Remember that it short-circuits evaluation when encountering null or undefined.