null, undefined, ?? and ?.
Tell null from undefined, supply defaults without clobbering 0 and empty text, and read nested data that may be missing.
What you will learn
- Say where undefined comes from and when to use null
- Use ?? instead of || for defaults, and explain the difference
- Use ?. to read through values that might be missing
- Use ??=, ||= and &&= for conditional assignment
On this page
Most programs deal with data that may not be there: a setting nobody set, a field a user left out, a lookup that found nothing. JavaScript has two values for “nothing”, and two modern operators that make handling them short. The general idea of a missing value is part of the fundamentals lesson on values and types. This lesson covers JavaScript’s version.
undefined and null
undefined means “no value has been given”. The language produces it for you in several situations.
let declared;console.log(declared);
const user = { name: "Ada" };console.log(user.age);
console.log([10, 20][5]);
const empty = null;console.log(empty, typeof empty, typeof undefined);console.log(null == undefined, null === undefined);undefinedundefinedundefinednull object undefinedtrue falseTwo things need a word of explanation. let declared; declares a variable without giving it a value, and the lesson on let and const covers let. { name: "Ada" } is an object, a record with named parts, and [10, 20] is an array, a list; the objects and arrays lessons come later.
In order: a variable declared without a value, a property that does not exist, and a position beyond the end of an array. All give undefined. Two more sources appear once you write functions: a parameter that was not passed, and a function that returns nothing.
null is a value you write yourself, to say “there is deliberately nothing here”. The language almost never produces null on its own. As the last lines show, typeof null is "object" (the old mistake from the first lesson of this module), null == undefined is true, and null === undefined is false.
A practical rule: let undefined be what the language gives you, and use null only when you want to state “empty” explicitly. Do not assign undefined yourself. Many style guides go further and avoid null altogether.
The nullish coalescing operator: ??
A common job is “use this value, or a default if it is missing”. The old way is ||, which uses the default for any falsy value, not only for missing ones. That removes legitimate 0, "" and false.
The operator ?? (ES2020) falls back only for null and undefined.
const count = 0;const label = "";const missing = null;
console.log(count || 10, count ?? 10);console.log(label || "none", label ?? "none");console.log(missing || "fallback", missing ?? "fallback");console.log(undefined ?? null ?? "last");console.log(false || null, false ?? "kept");console.log((null ?? 1) || 2);10 0nonefallback fallbacklastnull false1count || 10is10even thoughcountis0, a perfectly good value.count ?? 10keeps the0.label || "none"replaces an empty string, andlabel ?? "none"keeps it. That is the empty line in the output.- For
null, both operators use the fallback. ??chains: it returns the first value that is notnullorundefined.
If you want “the default for anything falsy”, || is still right. The skill is choosing on purpose. For settings, quantities and flags, ?? is nearly always what you mean.
Optional chaining: ?.
Reading a property of undefined or null throws a TypeError. The optional chaining operator ?. (ES2020) stops and gives undefined instead, when the value before it is null or undefined.
const user = { name: "Ada", profile: { address: null }, tags: ["math"] };
console.log(user.profile.address?.city);console.log(user.settings?.theme);console.log(user.tags?.[0], user.nothing?.[0]);console.log(user.greet?.());console.log(user?.profile?.address?.city.toUpperCase().length);undefinedundefinedmath undefinedundefinedundefinedWithout the ?., the same read stops the program. console.log(user.settings.theme) fails with:
TypeError: Cannot read properties of undefined (reading 'theme')Three forms exist: a?.b for a property, a?.[0] for an index or computed key, and a?.() to call a function only if it exists. In the example, user.profile.address?.city is undefined because address is null. The error above names the property that was read from undefined, which is the first thing to look at when you see it.
The last line of the program shows short-circuiting: when address is missing, the whole rest of the chain, including .toUpperCase() and .length, is skipped and the answer is undefined. You do not need a ?. after every dot.
Use ?. where data may be absent, not everywhere. If a value must be there, a TypeError that points to the problem is better than an undefined that hides it.
?. and ?? are a natural pair: read something that may be missing, then supply a default.
Conditional assignment: ??=, ||= and &&=
Three assignment operators combine a test with an assignment (ES2021). The assignment happens only when the test says so.
| Operator | Assigns when the left side is |
|---|---|
a ??= b |
null or undefined |
a ||= b |
falsy |
a &&= b |
truthy |
const options = { retries: 0, name: "", debug: undefined, verbose: true };
options.retries ??= 3;options.name ||= "anonymous";options.debug ??= false;options.verbose &&= "yes";
console.log(options);{ retries: 0, name: 'anonymous', debug: false, verbose: 'yes' }retries was 0 and stayed 0 with ??=. name was empty and was replaced with ||=. debug was undefined and got a default. verbose was truthy, so &&= replaced it.
Common pitfalls
- Using
||for defaults. It overwrites0,""andfalse. Use??. - Mixing
??with||or&&without brackets. It is a syntax error. - Chaining
?.everywhere. It hides real mistakes, such as a misspelt property name. - Checking for missing values with
typeof x === "undefined". Compare withundefineddirectly, or usex == null. - Assigning
undefinedto clear a value. Usenull, or delete the property.
A settings object config has an optional server object with host and port, and an optional debug flag. Build a new object settings with host (default "localhost"), port (default 8080) and debug (default false). A port of 0 and debug: false are valid and must be kept.
Start with const config = { server: { port: 0 }, debug: false };, then try {} and { server: { host: "example.test" }, debug: true } too.
Input: none. Expected output for the first config: { host: 'localhost', port: 0, debug: false }. For {}: { host: 'localhost', port: 8080, debug: false }. For the third: { host: 'example.test', port: 8080, debug: true }.
Show solution
const config = { server: { port: 0 }, debug: false };
const settings = { host: config.server?.host ?? "localhost", port: config.server?.port ?? 8080, debug: config.debug ?? false,};
console.log(settings);{ host: 'localhost', port: 0, debug: false }config.server?.host is undefined when server or host is missing, and ?? then supplies the default. Because ?? ignores 0 and false, the port: 0 is kept. Using || would have turned that port into 8080.
For each case choose || or ??, then write the line and print its result: (a) a page size of 0, where 0 means “show nothing”, with the fallback 10; (b) a display name of "", which should become "Guest"; (c) a volume of 0, where 0 means muted, with the fallback 50.
Input: none. Expected output: 0, Guest, 0.
Show solution
const pageSize = 0;const displayName = "";const volume = 0;
console.log(pageSize ?? 10);console.log(displayName || "Guest");console.log(volume ?? 50);0Guest0(a) ??, since 0 is a real choice. (b) ||, since the empty string should be replaced, and ?? would keep it. (c) ??, since a muted volume must stay 0.
Start with const prefs = { theme: "", fontSize: 0, language: undefined };. Use ||= for theme (an empty theme should become "light") and ??= for fontSize (default 16) and language (default "en"). Print prefs.
Input: none. Expected output: { theme: 'light', fontSize: 0, language: 'en' }.
Show solution
const prefs = { theme: "", fontSize: 0, language: undefined };
prefs.theme ||= "light";prefs.fontSize ??= 16;prefs.language ??= "en";
console.log(prefs);{ theme: 'light', fontSize: 0, language: 'en' }||= replaces the empty text. ??= leaves the 0 alone, because 0 is not null or undefined, and fills in the missing language.
Summary
undefinedis what the language gives for “not set”.nullis what you write for “deliberately empty”.a ?? bfalls back only fornullandundefined.a || bfalls back for any falsy value, including0and"".a?.b,a?.[i]anda?.()returnundefinedinstead of throwing whenais missing, and skip the rest of the chain.??=,||=and&&=assign conditionally.
Next: variables and control flow, starting with let, const and block scope.