The optional chaining operator (?.) in Javascript

Last updated on October 10, 2022

Building a real-world application means working with data, often stored as objects.

In the real world, data is often complex and nested multiple levels deep. An object has a property with the value of another object, and that object might store yet another one. To make things worse, data is often unpredictable – sometimes property values are null, undefined, or simply not there.

In this article, we will go over the optional chaining operator, a new JavaScript feature that allows you to check the existence of a property before accessing it.

A Common Solution without using ?.

For a long time, developers used the behavior of && logical operator to check the existence of a property before accessing it.

Let’s imagine we have a person object, with properties nested multiple levels deep.

const person = {
  name: "John Doe",
  age: 55,
  occupation: "farmer",
  address: {
    state: "North Carolina",
    city: "Lillington",
    street: "1937 Powell Farm Rd",
    zip: 27546,
    shipping_address: {
      street: "1937 Powell Farm Rd",
      zip: 27546,
    },
  },
};

A commonly used solution to check the existence of a property (let’s say ‘zip’) is to use the logical AND operator:

const zipCode = person.address && person.address.zip;

console.log(shippingZip);
// expected output: 27546

The logical AND operator checks if the value of person.address property is null or undefined. If it is, it performs an early exit and returns undefined.

If the value of person.address is a valid object, then the logical AND operator returns the nested value of person.address.zip property.

This syntax is fine if you want to access properties one or two levels deep.

The expression gets way too complicated when you want to access property multiple levels deep. For example, the zip code of the shipping address:

const shippingZip =
  person.address ??
  person.address.shipping_address ??
  person.address.shipping_address.zip;

console.log(shippingZip);
// expected output: 27546

Writing such repetitive, long, unreadable expressions is inefficient. That’s why Javascript introduced something called the optional chaining operator (denoted as ?.).

Javascript optional chaining operator - an example

The optional chaining operator provides simpler, more readable syntax to check the existence of properties before accessing them. It saves you the trouble of manually checking each reference in the chain.

The rules are simple – if there’s a chance that property doesn’t exist, simply use ?. notation instead of the normal dot notation on its right.

const zipCode = person.address?.zip;

// if the person object is within scope, zipCode will be set equal to 27546

The syntax is much cleaner. This is more noticeable when you’re chaining multiple property accesses.

const shippingZip = person.address?.shipping_address?.zip;

// if the person object is within scope, zipCode will be set equal to 27546

For me, the question mark signifies the uncertainty of the property’s existence. So the syntax is very readable and descriptive of its use case.

It works exactly like the solution with the logical AND operator.

If any property in the chain is null, undefined, or just doesn’t exist, the expression automatically short-circuits, returning undefined, saving you from a possible error.

Uncaught TypeError: Cannot read property of undefined

Calling a method using the optional chaining operator

An optional chaining operator can prevent an error when trying to call a method that doesn’t exist or is not available. (due to the version of the API, or incompatibility of the user’s device).

When trying to call methods that don’t exist, the optional chaining operator will short-circuit to undefined, as it does for properties.

If you try to call property as a method, you will still get an error:

TypeError obj.method is not a function

Check the existence of the object itself

The optional chaining operator is not restricted to checking the properties of objects. You can use it to check the existence of the object itself.

const zipCode = person?.address.zip;

// right-side expression will return undefined if the person object can not be found

Is just a shorter, more readable way to write:

const zipCode = person === undefined ? undefined : person.address.zip;

A possible use case for this feature is to avoid errors when you’re trying to access external API data before it is loaded.

Dynamic property access

Bracket notation allows you to dynamically determine the property you’re trying to access.

Here’s how to use JavaScript optional chaining operator with this syntax:

const zipCode = person?.[‘add’+’ress’].zip

// will be evaluated as person?.address.zip

JavaScript optional chaining operator with an array index

Bracket notation is often used to look up items at a specific index in the array.

An optional chaining operator can be useful if you’re unsure whether an array has an item at that index.

For example:

const arr = [1, 2, 3, 4];

const item = arr?.[5];

console.log(item);
// undefined

Array does not have 6 items, so there is no item on index 5. Thanks to optional chaining, JavaScript will not throw an error.

Nullish coalescing operator to set the default value

The optional chaining operator gives you the ability to access object properties when possible, but avoid errors if properties do not exist or are inaccessible.

You can go one step further and instead of short-circuiting to undefined, set a default value to be returned when a property value is inaccessible.

The syntax is simple: after you try to access a property, follow it with double question marks (??) to set the backup value.

const zipCode = person.address?.zip ?? "zip code not found";

console.log(zipCode);
// if property access is successful, the output will be the value of the property
// if property access is not successful, the output will be "zip code not found"

What not to do with JavaScript optional chaining operator

This syntax can not be used to assign value to a property of an object.

Using the optional chaining operator on the left side of the equation is wrong:

person.address?.zip = 19805 // this is wrong

Another common mistake is overusing the optional chaining operator, which makes your code hard to debug because it’s difficult to track down which part of the code returns undefined. This practice is called error silencing.

Practical use cases for JS optional chaining operator

Using this syntax can help you improve readability and reduce the length of your code.

Because of its simplicity, JavaScript developers often use optional chaining operators too much.

Let’s go over good use cases for this syntax.

Examples of Good Practical use cases

As a general rule of thumb, the optional chaining operator is best used with external data, which is often unpredictable.

For instance, when loading data from an API, a response can be delayed for one reason or another. Trying to access external data before it’s loaded can lead to errors, which could be avoided by using the optional chaining operator.

Using JavaScript optional chaining operator when filtering data

External data is often formatted as an array of objects. It’s common to use methods like find() and filter() to find objects that meet certain criteria.

For example, for an e-commerce store, each object can represent one product. An array can contain thousands of such objects. Naturally, the list of products is constantly updated - a store might add a new product and remove some of the old ones.

You might need to use these methods to filter the list of all products (objects) to display the ones that cost more than $10, for example.

Sometimes calling filter() and find() methods returns an empty array, as there are no qualifying objects. Alternatively, the property you are trying to access may be optional, and absent from certain objects.

You can use JavaScript optional chaining operator to access the properties of filtered objects. If the filter() or find() method returns no objects, expression will simply return undefined.

let products = [{name: "coca cola", cost: 4, inStock: true},..., {name: "fanta", cost: 3, inStock: true}]
let filteredProducts = products.filter(product => product.cost > 5)

console.log(filteredProducts?.[0])
// if filteredProducts is empty, the expression above will return undefined

Invite us to your inbox.

Articles, guides and interviews about web development and career progression.

Max 1-2x times per month.