27. Optional Chaining & Nullish Coalescing
Optional chaining (?.) and nullish coalescing (??) are modern JavaScript features that make it easy to access nested data without worrying about errors from null or undefined values.
The Problem Without Optional Chaining
// Data from an API — could be empty/undefined
const data = {
student: {
name: "Mike",
address: {
city: "New York"
// province is missing!
}
// phone is missing!
}
};
// Without optional chaining — you must check each level manually
const city = data && data.student && data.student.address
&& data.student.address.city;
const phone = data && data.student && data.student.phone;
const province = data && data.student && data.student.address
&& data.student.address.province;
// With optional chaining — concise and safe
const city = data?.student?.address?.city;
const phone = data?.student?.phone;
const province = data?.student?.address?.province;
Code Example
Optional Chaining for Property Access
const campusData = {
faculty: {
"Engineering Faculty": {
major: {
"Computer Science": {
chair: "Dr. John Smith",
capacity: 120
}
}
}
}
};
// Safe nested access
const chair = campusData?.faculty?.["Engineering Faculty"]?.major
?.["Computer Science"]?.chair;
console.log(chair); // "Dr. John Smith"
// If the path doesn't exist — return undefined (no error!)
const dean = campusData?.faculty?.["Economics Department"]?.dean;
console.log(dean); // undefined
// Without optional chaining, this line would throw a TypeError!
// const dean = campusData.faculty["Economics Department"].dean; // ERROR!
Optional Chaining for Method Calls
const student = {
name: "Mike",
getProfile() {
return { studentId: this.studentId, major: this.major };
}
// the printTicket method doesn't exist!
};
// Call a method that might not exist
const profile = student?.getProfile?.();
console.log(profile); // { studentId: undefined, major: undefined }
// Method doesn't exist — no error, returns undefined
const ticket = student?.printTicket?.();
console.log(ticket); // undefined
// Without ?. it would error:
// student.printTicket(); // TypeError: not a function
Optional Chaining for Array Access
const myClass = {
name: "TI-A",
students: [
{ name: "Mike", grade: [85, 90, 78] },
{ name: "Anna", grade: [92, 88] },
{ name: "John" } // no grade array!
]
};
// Access an array element that might not exist
const mikesFirstGrade = myClass?.students?.[0]?.grade?.[0];
console.log(mikesFirstGrade); // 85
const johnsGrade = myClass?.students?.[2]?.grade?.[0];
console.log(johnsGrade); // undefined (John has no grade array)
const outOfRangeGrade = myClass?.students?.[5]?.grade?.[0];
console.log(outOfRangeGrade); // undefined (index 5 doesn't exist)
Nullish Coalescing ??
The ?? operator returns the right-hand value only if the left-hand value is null or undefined (NOT 0, "", or false).
const inputUser = null;
const defaultName = inputUser ?? "Unknown";
console.log(defaultName); // "Unknown"
// Difference from || (OR)
const score = 0;
console.log(score || 100); // 100 (0 is considered falsy!)
console.log(score ?? 100); // 0 (0 is not null/undefined)
const name = "";
console.log(name || "Anonymous"); // "Anonymous" (empty string is falsy)
console.log(name ?? "Anonymous"); // "" (empty string is not null/undefined)
// Combining optional chaining + nullish coalescing
const data = {
student: {
name: "Mike",
address: null // exists but null
}
};
const address = data?.student?.address ?? "N/A";
console.log(address); // "N/A" (because it's null)
const city = data?.student?.address?.city ?? "Unknown";
console.log(city); // "Unknown" (because of null property access)
Common Pattern: API Response Handling
async function fetchStudentData(studentId) {
try {
const response = await fetch(`/api/student/${studentId}`);
const data = await response.json();
// Access nested data safely
const name = data?.data?.name ?? "Not found";
const email = data?.data?.contact?.email ?? "N/A";
const phone = data?.data?.contact?.phone ?? "N/A";
const gpa = data?.data?.academicSystem?.gpa ?? 0;
const classification = data?.data?.academicSystem?.classification ?? "Not Available";
// Safe template literal
const info = [
`Name: ${name}`,
`Email: ${email}`,
`Phone: ${phone}`,
`GPA: ${gpa}`,
`Classification: ${classification}`
].join("\n");
return info;
} catch (error) {
return "Failed to load student data";
}
}
Nested Object Traversal
const campusDatabase = {
student: [
{
name: "Mike",
organization: [
{
name: "HIMSI",
position: "Chair",
pengurus: [
{ name: "Anna", role: "Secretary" },
{ name: "John", role: "Bendahara" }
]
}
]
},
{
name: "Amy",
organization: [] // empty!
},
{
name: "John"
// no organization field
}
]
};
// Safe access to nested data
function getInfoOrganisasi(data, studentName) {
const student = data?.student?.find(m => m?.name === studentName);
if (!student) return "Student not found";
const firstOrganization = student?.organization?.[0];
if (!firstOrganization) return `${studentName} has not joined an organization`;
const chair = firstOrganization?.pengurus?.[0];
return `${firstOrganization.name} - Chair: ${chair?.name ?? "Not set"}`;
}
console.log(getInfoOrganisasi(campusDatabase, "Mike"));
// "HIMSI - Chair: Anna"
console.log(getInfoOrganisasi(campusDatabase, "Amy"));
// "Amy has not joined an organization"
console.log(getInfoOrganisasi(campusDatabase, "Anna"));
// "Student not found"
Nullish Coalescing Assignment ??=
// Assign only if no value exists yet
let className;
className ??= "TI-A"; // assigned because undefined
console.log(className); // "TI-A"
className ??= "TI-B"; // not assigned because a value already exists
console.log(className); // "TI-A"
// Useful for default config
const config = {
campusName: "Technology University",
maxStudent: null,
website: ""
};
// ??= only assigns when null/undefined
config.maxStudent ??= 100; // assigned (null → 100)
config.website ??= "university.edu"; // not assigned ("" is not null)
console.log(config.maxStudent); // 100
console.log(config.website); // "" (still empty)
Code Explanation
| Operator | When It Returns the Right Operand | Example Values That Trigger It |
|---|---|---|
\|\| |
Left is falsy | 0, "", false, null, undefined, NaN |
?? |
Left is null/undefined only | null, undefined |
?. |
If the path exists | Continue accessing the property |
??= |
If the left is null/undefined | Assign the default value |
Real-World Examples
A student registration form with data validation:
const registrationData = [
{
name: "Mike Johnson",
studentId: "2024001",
contact: {
email: "[email protected]",
phone: "08123456789"
},
scholarship: null
},
{
name: "Anna Reed",
studentId: "2024002"
// no contact, no scholarship
},
{
name: null,
studentId: "2024003",
contact: {
email: null,
phone: null
}
}
];
function processRegistration(data) {
return data.map(applicant => {
const name = applicant?.name ?? "Name unknown";
const email = applicant?.contact?.email ?? "Email not provided";
const phone = applicant?.contact?.phone ?? "No phone";
const paymentType = applicant?.scholarship?.name ?? "Self-pay";
const discount = applicant?.scholarship?.percentage ?? 0;
return {
name,
email,
phone,
paymentType,
discount,
fullStatus: (
applicant?.name &&
applicant?.contact?.email &&
applicant?.contact?.phone
) ? "Complete" : "Incomplete"
};
});
}
const result = processRegistration(registrationData);
console.log(JSON.stringify(result, null, 2));
// [
// { name: "Mike Johnson", email: "[email protected]", fullStatus: "Complete" },
// { name: "Anna Reed", email: "Email not provided", fullStatus: "Incomplete" },
// { name: "Name unknown", email: "Email not provided", fullStatus: "Incomplete" }
// ]
Example: Profile Display with Incomplete Data
function renderProfile(student) {
const container = document.getElementById("profile");
const html = `
<div class="card">
<h2>${student?.name ?? "Student"}</h2>
<p>StudentID: ${student?.studentId ?? "-"}</p>
<p>Major: ${student?.major?.name ?? "No selection yet"}</p>
<p>Year: ${student?.year ?? "-"}</p>
<p>Email: ${student?.contact?.email ?? "Not available"}</p>
<p>Phone: ${student?.contact?.phone ?? "Not available"}</p>
<p>GPA: ${student?.academicSystem?.gpa ?? 0}</p>
<p>Status: ${student?.status ?? "Active"}</p>
</div>
`;
container.innerHTML = html;
}
// Example of incomplete data
const profile = { name: "Mike", studentId: "2024001" };
renderProfile(profile);
// All empty fields are displayed safely without errors
Conclusion
?.(optional chaining) prevents TypeError when accessing a property/method that may not exist.??(nullish coalescing) returns the default value only for null/undefined, not for other falsy values.- The combination of
?.and??is very powerful for data from APIs or user input that can't be predicted. - Use
??=to assign a default value only if the field hasn't been filled in yet. - Distinguish
??from||:0and""are considered falsy by||but are not nullish for??.