Back to Blog
JavaScriptOptional ChainingES2020Syntax

JavaScript Optional Chaining Guide

Master JavaScript optional chaining for safe property access and avoiding undefined errors.

B
Bootspring Team
Engineering
February 11, 2020
5 min read

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); // undefined

Property 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); // false

Common 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 error

TypeScript 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.

Share this article

Help spread the word about Bootspring