completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Nullish coalescing & optional chaining

Handle missing values with ?? and ?. instead of long checks, while preserving useful values like 0, false, and the empty string.

By the end, you can
  • 01
    Choose the right backupUse ?? when only null and undefined should use the backup value. Know when || is still the right choice.
  • 02
    Read data that might be missingUse ?. before properties, square brackets, and calls. Know which errors it does not stop.
  • 03
    Fill in or update a valueUse ??=, ||=, and &&=, and know the check that each one makes.

Missing is not the same as empty

In the Logical operators lesson, you saw a problem with count || 10. If count is 0, you get 10, and the real 0 is lost. This lesson shows a tool made for that problem. It is called nullish coalescing, and you write it as ??.

A value is nullish when it is null or undefined. Those are the only two nullish values. Other falsy values are not nullish, and they can be real data:

  • 0 can be a volume that is turned all the way down.
  • false can be a checkbox that someone left unticked on purpose.
  • "", the empty string, can be a blank label that someone chose.
The main idea

Use ?? to pick a backup value only when the first value is null or undefined. Use ?. to read a property that might not be there. If the value before ?. is null or undefined, you get undefined instead of an error.

Real-life analogy?? is a backup plan

You are planning a trip, and you keep a backup plan too. You use the backup only when you have no plan at all. A plan that says "walk zero blocks" is still a plan. A plan that says "don't send a reminder" is still a plan.

In real life: You have no plan at all
In JavaScript: null or undefined: use the backup
In real life: Your plan costs ₹0
In JavaScript: 0: keep it
In real life: Your plan says no reminder
In JavaScript: false: keep it
In real life: Your plan has a blank name, on purpose
In JavaScript: "": keep it

The second tool is optional chaining, written ?.. It lets you read data that might be incomplete. You don't have to write a long check before every read.

The ?? backup operator

INTERACTIVE

left ?? right asks one question: "Is left equal to null or undefined?"

  • If yes, the result is right.
  • If no, the result is left, unchanged.
Keep real values, fill in missing onesPop out in the code editor (opens in a new tab)JavaScript
const displayName = undefined;
const savedVolume = 0;
const city = null;
console.log(displayName ?? "Anonymous", savedVolume ?? 50, city ?? "Unknown");

Line 1 sets displayName to undefined, so line 4 uses "Anonymous". Line 2 sets savedVolume to 0. That is a real value, so it stays 0. Line 3 sets city to null, so it becomes "Unknown". The output is Anonymous 0 Unknown.

Left: count || 10. The box count holds 0, and the result is 10, so the 0 is lost. Right: count ?? 10. The same box holds 0, and the result is 0, so the 0 is kept.count || 100count100 is lostcount ?? 100count00 is kept
Both boxes hold the real value 0. || sees a falsy value and uses the backup, 10. ?? sees a value that is not null or undefined, so it keeps the 0.

The two operators ask different questions. || asks, "Is the value falsy?" ?? asks only, "Is the value null or undefined?" Those are the two "nothing here" values from the Booleans, null & undefined lesson.

Like ||, ?? works from left to right. JavaScript looks at the left side first. It runs the right side only when the left side is null or undefined. This helps when the backup takes work, like calling a function.

A real value skips the backup functionPop out in the code editor (opens in a new tab)JavaScript
const savedName = "Asha";
function makeGuest() {
  console.log("backup checked");
  return "Guest";
}
console.log(savedName ?? makeGuest());

Line 1 stores the name "Asha". Lines 2 to 5 make a backup function. If it ran, it would print backup checked. Line 6 prints Asha. The backup function never runs, because "Asha" is not null or undefined.

Compare ?? with ||
Compare the two backupsPop out in the code editor (opens in a new tab)JavaScript
const value = 0;console.log(value || "default");console.log(value ?? "default");
Real resultDifferent
value
0
value || "default"
default
value ?? "default"
0
Try it yourself
Pick the value used on line 1

The two operators give different results here. || replaces any falsy value. ?? replaces only null or undefined.

Only line 1 changes. The results come from running real JavaScript, not from a list of guesses.

The table below tries each value with both operators. Read each row as: "when the value is this, || gives this, and ?? gives this."

Where || and ?? give different results
The valueWhat does || give?What does ?? give?Are they different?
0"default"0Yes. 0 is falsy, but it is not nullish.
"""default"""Yes. An empty string can be a real value, chosen on purpose.
false"default"falseYes. false is a real yes-or-no answer.
NaN"default"NaNYes. NaN is falsy, but it is not null or undefined.
null"default""default"No. Both use the backup.
undefined"default""default"No. Both use the backup.

So || is not wrong. It asks a different question: "Is the left side falsy?" Use || when you want to replace every falsy value, such as "", 0, false, and NaN. Use ?? when those values can be real data.

Reading safely with ?.

STEP THROUGH

In the Logical operators lesson, you saw long checks like user && user.address && user.address.city. Each && checks that one part is there before the next part is read. Optional chaining does the same job in a shorter way: user?.address?.city.

Here is what ?. does:

  • It looks at the value just before it.
  • If that value is null or undefined, it stops. The result is undefined, and there is no error.
  • If not, it reads the next part as usual.
Left: user.address. The box user holds null, and the read throws a TypeError. Right: user?.address. The same box holds null, and the read stops and gives undefined.user.addressnulluserTypeErrorstops with an erroruser?.addressnulluserundefinedstops, no error
In both, user holds null, so there is no address to read. A plain dot throws an error. ?. stops and gives undefined instead.
Real-life analogy?. checks that the door is there

You walk down a hallway. Before you open the next door, you check that the door is there. If there is no door, you stop. You don't walk into the wall.

In real life: There is no next door
In JavaScript: The value before ?. is null or undefined
In real life: You stop and turn back
In JavaScript: The result is undefined
In real life: The door is there
In JavaScript: JavaScript opens it and keeps reading
In real life: A door painted on the wall
In JavaScript: The property is there but is not a function, so ?.() still throws

You can put ?. in three places:

  • Before a property name: obj?.name.
  • Before square brackets: arr?.[0] or obj?.[key].
  • Before call brackets: fn?.() or obj.method?.(). The call happens only if the function is there.
Read a property, read an array item, and call a methodPop out in the code editor (opens in a new tab)JavaScript
const user = {
  name: "Asha",
  tags: ["reader"],
  greet() {
    return "Hi " + this.name;
  },
};
console.log(user?.name, user?.tags?.[0], user.greet?.());

Lines 1 to 7 make a user object. It has a name, a list of tags with one tag in it, and a greet method. Line 8 does three things: it reads the name, reads the first tag, and calls greet. Every part is there, so the output is Asha reader Hi Asha.

greet is called on user, so inside it, this is user. That is how greet can read the name.

Step through optional chaining
Step 0 of 9Ready
Your turn: follow the blue line

Pick a user, then step through each read. Before each step, guess: will it give a value, undefined, or a caught TypeError?

Running in
  1. script
Next: line 1
let checks = 0;function mark(label) {  checks += 1;  return label;}function riskyCity() {  try {    return user.address.city;  } catch (error) {    return error.name;  }}const safeCity = user?.address?.[mark("city")];const firstTag = user?.tags?.[0];const greeting = user?.greet?.();const noFunction = undefined;const optionalCall = noFunction?.();const notCallable = (() => {  try {    return ({ run: 7 }).run?.();  } catch (error) {    return error.name;  }})();const missingUser = null;const skipped = missingUser?.[mark("skipped")];console.log(riskyCity(), safeCity, firstTag, greeting, optionalCall, notCallable, checks, skipped);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Switch the user on line 1

Changing the user starts the replay again. Watch whether mark("city") runs on line 14.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Remember: ?. checks only the value just before it. It stops only when that value is null or undefined. If the value is there but is the wrong kind of value, the normal rules apply. For example, trying to call the number 7 with ?.() still throws a TypeError.

??=, ||=, and &&=

STEP THROUGH

These three operators are called logical assignment operators. Each one checks a variable or property first. Then it stores a new value only if the check passes.

  • x ??= y stores y only if x is null or undefined. It fills in a missing value.
  • x ||= y stores y only if x is falsy. It replaces any falsy value.
  • x &&= y stores y only if x is truthy. It updates a value that is already there.
Fill in with ??=, update with &&=Pop out in the code editor (opens in a new tab)JavaScript
const settings = { volume: 0, theme: undefined, nickname: "Asha" };
settings.volume ??= 50;
settings.theme ??= "light";
settings.nickname &&= settings.nickname.trim();
console.log(settings.volume, settings.theme, settings.nickname);

Line 1 makes a settings object. Line 2 keeps the volume at 0, because 0 is not nullish. Line 3 fills in the missing theme with "light". Line 4 runs its update because "Asha" is truthy. It stores the trimmed nickname. The .trim() method removes spaces from the start and end of a string. The output is 0 light Asha.

Left: volume ??= 50. The box volume already holds 0, so the backup 50 is not needed and volume stays 0. Right: theme ??= light. The box theme is empty, so the card light drops into it.volume ??= 5050not needed0volumestays 0theme ??= "light""light"themefilled in
??= stores the backup only in an empty place. The volume holds 0, which is a real value, so it stays. The theme holds nothing, so it gets "light".
Fill in, replace, or update a value
Step 0 of 7Ready
Your turn: follow the blue line

Step through the three logical assignments. Watch ??= keep 0, ||= replace an empty string, and &&= update only a value that is truthy.

Running in
  1. script
Next: line 1
const user = { nickname: "  Ada  " };settings.volume ??= 50;settings.theme ||= "light";settings.retries &&= settings.retries + 1;user.nickname &&= user.nickname.trim();console.log(settings.volume, settings.theme, settings.retries, user.nickname);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the starting object on line 1

Try "Volume is 0". You'll see ??= keep the 0, while ||= still replaces the empty theme.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

A common beginner mistake is using ||= for a setting where 0 is a real choice. volume ||= 50 would turn a muted volume back up to 50. Use ??=, so a muted volume stays muted.

Code that JavaScript refuses to run

SYNTAX

JavaScript refuses to run a few lines that look fine at first. The first rule: you can't write ?? next to && or || without round brackets. The round brackets show which part runs first.

SyntaxError: ?? next to || with no bracketsPop out in the code editor (opens in a new tab)JavaScript
const a = null;
const b = "backup";
const c = "last";
console.log(a || b ?? c);
Valid: round brackets show the orderPop out in the code editor (opens in a new tab)JavaScript
const a = null;
const b = "backup";
const c = "last";
console.log((a || b) ?? c);

In the valid version, the brackets tell JavaScript to work out a || b first. a is null, so a || b gives "backup". That value is not nullish, so ?? c keeps it. The output is backup.

The second rule: you can't put ?. on the left side of =. If obj were missing, JavaScript would have nowhere to store the new value. So it refuses the line before running anything.

SyntaxError: ?. on the left of =Pop out in the code editor (opens in a new tab)JavaScript
const obj = { a: 1 };
obj?.a = 2;

The last case is a different error. ?. can't help with a variable name that doesn't exist.

?. can't fix a missing variablePop out in the code editor (opens in a new tab)JavaScript
console.log(missingUser?.name);

This throws a ReferenceError. The problem is not name. The problem is that JavaScript can't find a variable called missingUser at all. ?. can stop at a null or undefined value. It can't create a variable, and it can't fix a typo in a name.

Don't hide data that must be there

Don't add ?. everywhere just to avoid errors. If a value must always be there, an error is useful. A TypeError tells you right away that something in your data or your program is wrong.

Where you’ll use this

You'll see these operators in code that reads user profiles, saved settings, optional functions, and data that comes from outside your program. Objects and arrays get their own lessons in Stage 3. For now, read them as simple containers:

  • { name: "Ada" } is an object. It has named properties, like name.
  • ["admin"] is an array. It has items that you read by position, starting at 0.
Format a user profilePop out in the code editor (opens in a new tab)JavaScript
const user = { name: "Asha", address: { city: "Pune" }, nickname: "" };
const displayName = user.nickname ?? user.name ?? "Guest";
const city = user.address?.city ?? "Unknown";
const firstRole = user.roles?.[0] ?? "reader";
console.log(displayName, city, firstRole);

Line 2 keeps the empty nickname, because ?? treats "" as a real value. Line 3 reads the city, "Pune". Line 4 finds no roles, so ?. gives undefined, and ?? uses "reader". Line 5 prints the empty nickname, then Pune reader.

What if your app says a blank nickname should use the name instead? Then line 2 should use ||. Pick the operator that matches the rule your app needs.

This matters when a form is only partly filled in. These are two different cases:

  • The whole contact object is missing.
  • The contact object is there, but its phone is blank.

?. handles the first case. Then your app's rule decides if a blank phone is OK.

Missing contact, or blank phone?Pop out in the code editor (opens in a new tab)JavaScript
const profile = { contact: { phone: "" } };
const savedPhone = profile.contact?.phone ?? "No phone saved";
const displayPhone = profile.contact?.phone || "Add a phone number";
console.log(savedPhone, displayPhone);

Line 1 makes a profile with a contact object. Its phone is an empty string, on purpose. Line 2 keeps that empty string, because ?? sees a real value. Line 3 gives "Add a phone number", because || replaces an empty string. Line 4 prints an empty value first, then Add a phone number.

Read profile.contact?.phone ?? "No phone saved" in two steps:

  1. ?. tries to read the phone. It gives undefined only if a part is missing.
  2. ?? replaces that undefined with the backup text.
The profile cart has no contact inside. Step 1: ?. finds no contact and gives undefined. Step 2: ?? replaces undefined with the text No phone saved.profile.contact?.phone ?? "No phone saved"profileundefined"No phone saved"1. ?. finds no contact2. ?? uses the backup
Two steps, two jobs. First ?. tries to read the phone and finds no contact, so it gives undefined. Then ?? sees undefined and uses the backup text.

Each operator has its own job. That makes the code easy to change when your app's rule changes.

?? or ||?
  • A saved volume can be 0, which means muted.
  • A product price can be 0 for a free item.
  • A username must not be empty, so "" should show Guest.
  • A menu label should use a backup when it is "".
  • A saved checkbox can be false, which means the person said no.
  • A search box should show All items when the search text is blank.
Try it yourself
0 of 6 correct

Sort each case. Pick ?? if falsy values like 0 or false should be kept. Pick || if they should be replaced.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common misconceptions

  • “Nullish means falsy.” No. Nullish means only null or undefined. Falsy also includes 0, false, "", and NaN.
  • “?? is a better ||.” No. They ask different questions, so each one is right for a different job.
  • “?. makes a whole line safe.” No. It checks only the value just before it.
  • “method?.() checks that it is a function.” No. It skips only a missing method. A value that is not a function still throws a TypeError.
  • “Logical assignment is just shorter.” It is shorter, and it also shows the exact check that decides whether to store.
  • “Use ?. everywhere.” No. If data must be there, let the error show, so you can find the bug.

The table below compares six tools that look alike. Read each row as: "this tool checks this, it is useful for this, and watch out for this."

Six tools that look alike, and what each one checks
ToolWhat does it check?When is it useful?What should you watch for?
??Is the value null or undefined?It keeps 0, false, and blank strings.It does not reject a blank string.
||Is the value falsy?It replaces any blank, zero, false, or NaN value.It can replace a real choice, like a muted volume.
?.Is the value before it null or undefined?It reads data that might not be there.It can hide a bug when the data must be there.
??=Is the stored value null or undefined?It fills in missing settings.It keeps 0, on purpose.
||=Is the stored value falsy?It replaces empty labels on a page.It also replaces 0 and false.
&&=Is the stored value truthy?It updates a string or flag that is already there.It skips empty strings and 0.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict the two backup results

Work out the output in your head. Type both output lines in order.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const saved = 0;
console.log(saved || 10);
console.log(saved ?? 10);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeFix a volume default that breaks for 0

    The app should keep a saved volume of 0. Change the operator that picks the default.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const savedVolume = 0;
    const volume = savedVolume || 50;
    console.log(volume);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeRewrite a long check with ?.

      Replace the repeated && checks with optional chaining.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const city = user && user.address && user.address.city;
      console.log(city || "Unknown");

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeAdd defaults with ??=

        Write two logical assignment lines. Keep the saved volume, and add a theme.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const settings = { volume: 0 };
        // Fill volume with 50 only if missing.
        // Fill theme with "light" only if missing.
        console.log(settings.volume, settings.theme);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeSpot the SyntaxError when ?? sits next to ||

          What must you add to make line 4 valid JavaScript?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const a = null;
          const b = "backup";
          const c = "last";
          console.log(a || b ?? c);

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeKeep real saved preferences

            A settings page saved two choices on purpose: no email updates, and a page size of 0. Predict the output after the two default lines run. Real websites use this same rule when they load saved settings.

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            const preferences = { emailUpdates: false, pageSize: 0 };
            preferences.emailUpdates ??= true;
            preferences.pageSize ??= 20;
            console.log(preferences.emailUpdates, preferences.pageSize);

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              7 QUESTIONS
              Nullish and optional chaining quiz · 7 questionsScore: first tries count
              1. Question 1 of 7When does left ?? right use right?

                Choose an answer to see the explanation.

              2. Question 2 of 7What does the count ?? 10 code print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const count = 0;
                console.log(count ?? 10);

                Choose an answer to see the explanation.

              3. Question 3 of 7What does this read of the user's city print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const user = { address: { city: "Pune" } };
                console.log(user?.address?.city);

                Choose an answer to see the explanation.

              4. Question 4 of 7What does this code print when the user is null?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const user = null;
                console.log(user?.name ?? "Guest");

                Choose an answer to see the explanation.

              5. Question 5 of 7Which line can throw an error even though it uses ?.?

                Choose an answer to see the explanation.

              6. Question 6 of 7What does the ??= volume code print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const settings = { volume: 0 };
                settings.volume ??= 50;
                console.log(settings.volume);

                Choose an answer to see the explanation.

              7. Question 7 of 7Which line is valid JavaScript?

                Choose an answer to see the explanation.

              Key takeaways

              • ?? uses the backup only for null and undefined.
              • || uses the backup for every falsy value, so it can lose 0, false, and blank strings.
              • ?. gives undefined when the value before it is null or undefined.
              • ?.() skips a missing function, but a value that is not a function still throws a TypeError.
              • ??=, ||=, and &&= store a new value only when their check passes.
              • Use round brackets when you put ?? next to && or ||.

              Final definition: ?? gives a backup for a missing value. ?. stops a property read or a call at a missing part and gives undefined.

              Coming next: The rest of the operators.

              CompleteFrontend Clear concepts. Working examples.