Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
LoginSign Up
menu
  1. Home
  2. /Web Development
  3. /Javascript
  4. /Optional Chaining Nullish Coalescing
Your Progress0%
0 of 61 completed

JavaScript Topics

Getting Started

  • What is JavaScript?
  • JavaScript vs Other Languages
  • Setting Up Your JavaScript Environment
  • Developer Console and Debugging Basics

JavaScript Fundamentals

  • Variables (var, let, const)
  • Data Types and Type Checking
  • Comments and Code Organization
  • String Methods and Template Literals
  • Type Conversion and Coercion
  • Operators (Arithmetic, Comparison, Logical)

Control Flow

  • If/Else Statements
  • Switch Statements
  • Ternary Operator
  • Truthy and Falsy Values
  • Logical Operators and Short-Circuiting

Loops and Iteration

  • For Loops
  • While and Do-While Loops
  • For...of and For...in Loops
  • Break and Continue Statements

Functions

  • Function Declarations and Expressions
  • Arrow Functions
  • Function Parameters and Arguments
  • Return Statements and Values
  • Scope and Closures
  • Higher-Order Functions and Callbacks

Arrays

  • Array Basics and Creation
  • Array Methods: Adding and Removing Elements
  • Array Iteration Methods (forEach, map, filter)
  • Array Methods: find, some, every, reduce
  • Sorting, Reversing, and Array Transformation

Objects

  • Object Basics and Properties
  • Object Methods and 'this' Keyword
  • Object Destructuring
  • Object.keys, values, entries
  • Spread Operator and Object Cloning

DOM Manipulation

  • Introduction to the DOM
  • Selecting Elements (querySelector, getElementById)
  • Modifying Elements (textContent, innerHTML, style)
  • Creating and Removing Elements
  • Event Listeners and Event Handling
  • Event Object and Event Delegation

Asynchronous JavaScript

  • Understanding Asynchronous Code
  • setTimeout and setInterval
  • Callbacks and Callback Hell
  • Promises and Promise Chaining
  • Async/Await

Error Handling

  • Try, Catch, Finally
  • Throwing Custom Errors

Modern JavaScript Features

  • ES6+ Overview
  • Modules (import/export)
  • Optional Chaining and Nullish Coalescing
  • Rest and Spread Operators

Working with Data

  • JSON (Parse and Stringify)
  • Fetch API and HTTP Requests
  • Working with Local Storage

Advanced Concepts

  • Classes and Object-Oriented Programming
  • Array and Object Advanced Patterns
  • Regular Expressions Basics

Best Practices & Real-World Application

  • JavaScript Best Practices
  • Debugging Techniques and Tools
  • Building a Complete Interactive Project

Optional Chaining and Nullish Coalescing

Safe property access and smart defaults

Working with nested objects and potentially null/undefined values is common in JavaScript, but traditionally required verbose null checks to avoid errors. Modern JavaScript introduces two elegant operators: optional chaining (?.) for safe property access and nullish coalescing (??) for smart default values. These operators dramatically simplify code while making it more robust. Let's master these powerful features!

Optional Chaining (?.)

The Problem

JAVASCRIPT
// Without optional chaining - verbose and error-prone
let user = {
  name: 'Alice',
  address: {
    city: 'NYC',
    zip: '10001'
  }
};

// Accessing nested properties safely
let city;
if (user && user.address && user.address.city) {
  city = user.address.city;
}

console.log(city);  // "NYC"

// If user is null, this crashes!
let user2 = null;
// let city2 = user2.address.city;  // TypeError: Cannot read property 'address' of null

// Need defensive checks everywhere
let city3;
if (user2 && user2.address && user2.address.city) {
  city3 = user2.address.city;
}

console.log(city3);  // undefined (no error)

The Solution: Optional Chaining

JAVASCRIPT
// With optional chaining - clean and safe!
let user = {
  name: 'Alice',
  address: {
    city: 'NYC',
    zip: '10001'
  }
};

// Safe property access
let city = user?.address?.city;
console.log(city);  // "NYC"

// If any part is null/undefined, returns undefined (no error!)
let user2 = null;
let city2 = user2?.address?.city;
console.log(city2);  // undefined (no crash!)

// Works with undefined too
let user3;
let city3 = user3?.address?.city;
console.log(city3);  // undefined

// Short-circuits at first null/undefined
let user4 = { name: 'Bob' };  // No address property
let city4 = user4?.address?.city;
console.log(city4);  // undefined (address is undefined)

?. makes code cleaner and safer

Without Optional Chaining

// Old way: Manual null checks
let user = getUser();

let city;
if (user && 
    user.address && 
    user.address.city) {
  city = user.address.city;
}

let street;
if (user && 
    user.address && 
    user.address.street) {
  street = user.address.street;
}

// Deeply nested - even worse!
let value;
if (data && 
    data.results && 
    data.results[0] && 
    data.results[0].user &&
    data.results[0].user.profile) {
  value = data.results[0].user.profile.name;
}
Output:
Not run yet...

With Optional Chaining

// Modern way: Optional chaining
let user = getUser();

let city = user?.address?.city;

let street = user?.address?.street;

// Clean, readable, safe!

// Deeply nested - much cleaner!
let value = data
  ?.results
  ?.[0]
  ?.user
  ?.profile
  ?.name;

// So much better!
Output:
Not run yet...

Optional Chaining Usage

1. Object Property Access

JAVASCRIPT
let user = {
  name: 'Alice',
  address: {
    city: 'NYC'
  }
};

// Normal property access
console.log(user?.name);           // "Alice"
console.log(user?.address?.city);  // "NYC"
console.log(user?.phone);          // undefined
console.log(user?.address?.street); // undefined

// With null/undefined objects
let user2 = null;
console.log(user2?.name);  // undefined (no error)

let user3 = undefined;
console.log(user3?.address?.city);  // undefined (no error)

2. Array Element Access

JAVASCRIPT
let users = [
  { name: 'Alice' },
  { name: 'Bob' },
  { name: 'Charlie' }
];

// Safe array access
console.log(users?.[0]?.name);    // "Alice"
console.log(users?.[10]?.name);   // undefined (index doesn't exist)

// With null/undefined arrays
let users2 = null;
console.log(users2?.[0]?.name);   // undefined (no error)

// Dynamic index
let index = 1;
console.log(users?.[index]?.name);  // "Bob"

// Nested arrays
let data = {
  results: [
    { items: [1, 2, 3] },
    { items: [4, 5, 6] }
  ]
};

console.log(data?.results?.[0]?.items?.[1]);  // 2
console.log(data?.results?.[5]?.items?.[0]);  // undefined

3. Function/Method Calls

JAVASCRIPT
let user = {
  name: 'Alice',
  greet: function() {
    return 'Hello!';
  }
};

// Safe method call
console.log(user.greet?.());  // "Hello!"

// If method doesn't exist
console.log(user.sayBye?.());  // undefined (no error)

// With null object
let user2 = null;
console.log(user2?.greet?.());  // undefined (no error)

// Practical example: Optional callback
function processData(data, callback) {
  let result = transform(data);
  
  // Call callback only if it exists
  callback?.(result);
  
  return result;
}

// With callback
processData(data, (result) => console.log(result));

// Without callback (no error)
processData(data);

// API example
let api = {
  user: {
    getData: async () => {
      return { name: 'Alice' };
    }
  }
};

// Safe async method call
let data = await api?.user?.getData?.();
console.log(data);  // { name: 'Alice' }

4. Combining Optional Chaining

JAVASCRIPT
// Complex nested structure
let response = {
  data: {
    users: [
      {
        profile: {
          getName: () => 'Alice'
        }
      }
    ]
  }
};

// Chain property access, array access, and method call
let name = response
  ?.data
  ?.users
  ?.[0]
  ?.profile
  ?.getName
  ?.();

console.log(name);  // "Alice"

// If any part is null/undefined, returns undefined
let response2 = { data: { users: [] } };
let name2 = response2?.data?.users?.[0]?.profile?.getName?.();
console.log(name2);  // undefined (no crash!)

Nullish Coalescing (??)

The Problem with OR (||)

JAVASCRIPT
// Problem: || treats ALL falsy values as falsy
// Falsy values: false, 0, '', null, undefined, NaN

let count = 0;
let value1 = count || 10;
console.log(value1);  // 10 (but we wanted 0!)

let message = '';
let value2 = message || 'Default';
console.log(value2);  // "Default" (but we wanted '')

let isActive = false;
let value3 = isActive || true;
console.log(value3);  // true (but we wanted false!)

// || replaces ALL falsy values with default
// This is often NOT what we want!

The Solution: Nullish Coalescing

JAVASCRIPT
// ?? only treats null and undefined as "nullish"
// It keeps other falsy values (0, '', false)

let count = 0;
let value1 = count ?? 10;
console.log(value1);  // 0 (keeps 0!)

let message = '';
let value2 = message ?? 'Default';
console.log(value2);  // '' (keeps empty string!)

let isActive = false;
let value3 = isActive ?? true;
console.log(value3);  // false (keeps false!)

// Only replaces null/undefined
let value4 = null ?? 'Default';
console.log(value4);  // "Default"

let value5 = undefined ?? 'Default';
console.log(value5);  // "Default"

// Perfect for default values!
let settings = {
  volume: 0,        // 0 is valid
  brightness: 50,
  notifications: false  // false is valid
};

let volume = settings.volume ?? 100;
console.log(volume);  // 0 (not 100!)

let notifications = settings.notifications ?? true;
console.log(notifications);  // false (not true!)

?? is safer for defaults

|| Operator

// OR operator (||)
// Treats ALL falsy as falsy

let count = 0;
console.log(count || 10);  // 10

let message = '';
console.log(message || 'Hi');  // "Hi"

let ready = false;
console.log(ready || true);  // true

// Problem: Replaces valid values!

// Settings with defaults
let volume = settings.volume || 50;
// If volume is 0, uses 50 (wrong!)

let muted = settings.muted || false;
// If muted is false, uses false (ok by luck)
Output:
Not run yet...

?? Operator

// Nullish coalescing (??)
// Only null/undefined are nullish

let count = 0;
console.log(count ?? 10);  // 0

let message = '';
console.log(message ?? 'Hi');  // ''

let ready = false;
console.log(ready ?? true);  // false

// Keeps ALL valid values!

// Settings with defaults
let volume = settings.volume ?? 50;
// If volume is 0, keeps 0 (correct!)

let muted = settings.muted ?? false;
// Only uses false if undefined
Output:
Not run yet...

Combining ?. and ??

JAVASCRIPT
// Powerful combination: optional chaining + nullish coalescing
let user = {
  name: 'Alice',
  settings: {
    theme: 'dark',
    notifications: false
  }
};

// Get nested value with default
let theme = user?.settings?.theme ?? 'light';
console.log(theme);  // "dark"

let language = user?.settings?.language ?? 'en';
console.log(language);  // "en" (default, property doesn't exist)

// Handles null user gracefully
let user2 = null;
let theme2 = user2?.settings?.theme ?? 'light';
console.log(theme2);  // "light" (user is null)

// With methods
let config = {
  getTimeout: () => 0  // 0 is valid timeout
};

let timeout = config?.getTimeout?.() ?? 5000;
console.log(timeout);  // 0 (not 5000!)

// If method doesn't exist
let config2 = {};
let timeout2 = config2?.getTimeout?.() ?? 5000;
console.log(timeout2);  // 5000 (method undefined)

// Complex example
let api = {
  users: [
    { name: 'Alice', age: 25 },
    { name: 'Bob' }  // No age
  ]
};

let firstUserAge = api?.users?.[0]?.age ?? 0;
console.log(firstUserAge);  // 25

let secondUserAge = api?.users?.[1]?.age ?? 0;
console.log(secondUserAge);  // 0 (age is undefined)

let thirdUserAge = api?.users?.[2]?.age ?? 0;
console.log(thirdUserAge);  // 0 (user doesn't exist)

Practical Examples

Example 1: API Response Handling

JAVASCRIPT
// Handling API responses safely
async function getUserProfile(userId) {
  try {
    let response = await fetch(`/api/users/${userId}`);
    let data = await response.json();
    
    // Safe access with defaults
    return {
      name: data?.user?.name ?? 'Anonymous',
      email: data?.user?.email ?? 'No email',
      avatar: data?.user?.profile?.avatar ?? '/default-avatar.png',
      bio: data?.user?.profile?.bio ?? '',
      location: data?.user?.profile?.location?.city ?? 'Unknown',
      posts: data?.user?.posts?.length ?? 0,
      followers: data?.user?.stats?.followers ?? 0,
      verified: data?.user?.verified ?? false
    };
  } catch (error) {
    // Return defaults if fetch fails
    return {
      name: 'Anonymous',
      email: 'No email',
      avatar: '/default-avatar.png',
      bio: '',
      location: 'Unknown',
      posts: 0,
      followers: 0,
      verified: false
    };
  }
}

// Usage
let profile = await getUserProfile(123);
console.log(profile.name);  // Always has a value

Example 2: Configuration Management

JAVASCRIPT
// Managing app configuration with safe defaults
class Config {
  constructor(userConfig) {
    this.config = userConfig;
  }
  
  get(path, defaultValue) {
    // Split path: "api.timeout" -> ["api", "timeout"]
    let keys = path.split('.');
    let value = this.config;
    
    // Navigate path safely
    for (let key of keys) {
      value = value?.[key];
      if (value === undefined) break;
    }
    
    // Return value or default
    return value ?? defaultValue;
  }
}

// User provides partial config
let userConfig = {
  api: {
    baseURL: 'https://api.example.com',
    timeout: 0  // 0 is valid!
  },
  features: {
    analytics: false  // false is valid!
  }
};

let config = new Config(userConfig);

console.log(config.get('api.baseURL', 'http://localhost'));     // User value
console.log(config.get('api.timeout', 5000));                   // 0 (not default!)
console.log(config.get('api.retries', 3));                      // 3 (default)
console.log(config.get('features.analytics', true));            // false (not default!)
console.log(config.get('features.darkMode', true));             // true (default)
console.log(config.get('database.host', 'localhost'));          // 'localhost' (default)

Example 3: Form Data Extraction

JAVASCRIPT
// Safely extract form data with validation
function extractFormData(form) {
  // Safely get form elements
  let elements = form?.elements;
  
  if (!elements) {
    return null;
  }
  
  return {
    // Text inputs (keep empty strings)
    username: elements.username?.value ?? '',
    email: elements.email?.value ?? '',
    
    // Number inputs (keep 0)
    age: parseInt(elements.age?.value) ?? null,
    salary: parseFloat(elements.salary?.value) ?? 0,
    
    // Checkboxes (keep false)
    newsletter: elements.newsletter?.checked ?? false,
    terms: elements.terms?.checked ?? false,
    
    // Select dropdowns
    country: elements.country?.value ?? 'US',
    
    // Radio buttons
    gender: elements.gender?.value ?? 'prefer-not-to-say',
    
    // Optional fields
    phone: elements.phone?.value ?? null,
    website: elements.website?.value ?? null
  };
}

// Usage
let form = document.getElementById('registrationForm');
let data = extractFormData(form);

console.log(data);
// {
//   username: "alice",
//   email: "alice@test.com",
//   age: 25,
//   salary: 0,              // Keeps 0!
//   newsletter: false,      // Keeps false!
//   terms: true,
//   country: "US",
//   gender: "female",
//   phone: null,
//   website: null
// }

Example 4: Event Handler Safety

JAVASCRIPT
// Safe event handling with optional chaining
class EventManager {
  constructor() {
    this.listeners = {};
  }
  
  on(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event].push(callback);
  }
  
  emit(event, data) {
    // Safely call all listeners
    this.listeners[event]?.forEach(callback => {
      try {
        callback?.(data);
      } catch (error) {
        console.error(`Error in ${event} listener:`, error);
      }
    });
  }
  
  off(event, callback) {
    // Safely remove listener
    let listeners = this.listeners[event];
    if (!listeners) return;
    
    let index = listeners.indexOf(callback);
    if (index > -1) {
      listeners.splice(index, 1);
    }
  }
}

// Usage
let events = new EventManager();

events.on('userLogin', (user) => {
  console.log(`Welcome, ${user?.name ?? 'Guest'}!`);
});

events.on('userLogin', (user) => {
  // This listener might throw
  console.log(user.profile.avatar);  // Might error
});

// Emit safely (second listener won't crash the first)
events.emit('userLogin', { name: 'Alice' });

// Emit with null data (handlers don't crash)
events.emit('userLogin', null);

// Emit event with no listeners (doesn't crash)
events.emit('nonExistentEvent', {});

Optional Chaining & Nullish Coalescing

Master safe access and smart defaults

Output:
Click "Run" to execute your code...
💡 Tip: Use console.log() to see your output in the console above.

Key Takeaways

  • ?. (optional chaining) safely accesses nested properties
  • Returns undefined if any part is null/undefined
  • Works with properties, arrays, and function calls
  • ?? (nullish coalescing) provides default values
  • Only treats null and undefined as nullish
  • Keeps 0, '', and false (unlike ||)
  • Combine ?. and ?? for safe access with defaults
  • Perfect for API responses and configuration
  • Eliminates verbose null checking code
  • Makes code cleaner, safer, and more maintainable

What's Next?

You now understand optional chaining and nullish coalescing—two powerful operators that make handling null/undefined values elegant and safe! You've learned when to use each and how to combine them for maximum effect.

In the final Modern JavaScript lesson, we'll explore Rest and Spread Operators—the versatile ... syntax for collecting and spreading values in arrays, objects, and function parameters!

💪 Practice Challenge:

Before moving on, try:

  1. Replace nested if checks with optional chaining
  2. Use ?? instead of || for default values where 0 or false are valid
  3. Combine ?. and ?? to safely extract API data with defaults
  4. Handle deeply nested optional properties in configuration
  5. Use ?. for safe method calls on potentially undefined objects

Test Your Understanding

Question 1 / 4

What does the optional chaining operator (?.) do?

Score: 0 / 0

Mastering safe property access with ?. and ?? ! 🎯

Previous
Modules (import/export)
Next
Rest and Spread Operators

Continue Learning JavaScript

Join 2,000+ developers mastering JavaScript. New lessons weekly - 100% FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

JavaScript Tutorials

0 of 61 completed

Your Progress0%

Topics

Getting Started

  • What is JavaScript?
  • JavaScript vs Other Languages
  • Setting Up Your JavaScript Environment
  • Developer Console and Debugging Basics

JavaScript Fundamentals

  • Variables (var, let, const)
  • Data Types and Type Checking
  • Comments and Code Organization
  • String Methods and Template Literals
  • Type Conversion and Coercion
  • Operators (Arithmetic, Comparison, Logical)

Control Flow

  • If/Else Statements
  • Switch Statements
  • Ternary Operator
  • Truthy and Falsy Values
  • Logical Operators and Short-Circuiting

Loops and Iteration

  • For Loops
  • While and Do-While Loops
  • For...of and For...in Loops
  • Break and Continue Statements

Functions

  • Function Declarations and Expressions
  • Arrow Functions
  • Function Parameters and Arguments
  • Return Statements and Values
  • Scope and Closures
  • Higher-Order Functions and Callbacks

Arrays

  • Array Basics and Creation
  • Array Methods: Adding and Removing Elements
  • Array Iteration Methods (forEach, map, filter)
  • Array Methods: find, some, every, reduce
  • Sorting, Reversing, and Array Transformation

Objects

  • Object Basics and Properties
  • Object Methods and 'this' Keyword
  • Object Destructuring
  • Object.keys, values, entries
  • Spread Operator and Object Cloning

DOM Manipulation

  • Introduction to the DOM
  • Selecting Elements (querySelector, getElementById)
  • Modifying Elements (textContent, innerHTML, style)
  • Creating and Removing Elements
  • Event Listeners and Event Handling
  • Event Object and Event Delegation

Asynchronous JavaScript

  • Understanding Asynchronous Code
  • setTimeout and setInterval
  • Callbacks and Callback Hell
  • Promises and Promise Chaining
  • Async/Await

Error Handling

  • Try, Catch, Finally
  • Throwing Custom Errors

Modern JavaScript Features

  • ES6+ Overview
  • Modules (import/export)
  • Optional Chaining and Nullish Coalescing
  • Rest and Spread Operators

Working with Data

  • JSON (Parse and Stringify)
  • Fetch API and HTTP Requests
  • Working with Local Storage

Advanced Concepts

  • Classes and Object-Oriented Programming
  • Array and Object Advanced Patterns
  • Regular Expressions Basics

Best Practices & Real-World Application

  • JavaScript Best Practices
  • Debugging Techniques and Tools
  • Building a Complete Interactive Project
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo