Making decisions · Lesson 11 of 77 · concept
and, or, and the nullish fallback
About 55 minutes.
By the end of this lesson you can
- Combine two conditions with && and || to make one condition.
- Explain short-circuit evaluation and say which value && and || actually produce.
- Choose ?? instead of || when 0 or empty text is a legitimate value.
- Parenthesise a mixed expression, because JavaScript refuses to guess.
When one question is not enough
Explanation
Real rules rarely fit in one comparison. A child may ride the rollercoaster if they are tall enough AND accompanied by an adult. A file should be rejected if it is too big OR of the wrong kind. You could write these as an if inside an if, and sometimes that reads best - but usually you want to say the whole rule in one line, the way you said it out loud.
&& is 'and': the whole thing is true only when both sides are true. || is 'or': the whole thing is true when at least one side is true. That second one is worth stating carefully, because everyday English often means 'one or the other but not both'. JavaScript's || means 'at least one', and it is perfectly happy when both are true.
The two characters are doubled for a reason: single & and | exist and do something entirely different, arithmetic on the individual bits of a number. If you type one by accident you will get a number instead of a decision, so it is worth noticing now that the operators you want always come in pairs.
They do not produce true or false - they hand back one of your values
A mental model
This is the part that surprises everyone, and once it clicks a great deal of real-world JavaScript stops looking cryptic. && and || do not convert anything to a boolean and hand it back. They pick one of the two values you gave them and return it unchanged.
The rule for ||: look at the left side. If it is truthy, that is the answer - return it. Otherwise return the right side, whatever it is. The rule for &&: look at the left side. If it is falsy, that is the answer - return it. Otherwise return the right side.
So "ann" || "guest" produces "ann", and "" || "guest" produces "guest" - which is why || is the classic way to supply a default. And 0 && 5 produces 0, not false. When both sides happen to be booleans you get a boolean out, which is why the behaviour is invisible in an ordinary if and only shows up when you start using the result as a value.
Short-circuit: the right-hand side may never run at all
Explanation
It follows from those rules that the right-hand side is only evaluated when it is needed. If the left side of an && is already falsy, the answer is decided and JavaScript does not look at the right side. If the left side of an || is already truthy, likewise. This is called short-circuit evaluation.
Most of the time this is just efficiency and you would never notice. It matters when the right-hand side DOES something - calls a function, changes a variable, prints a line. Then whether that happens depends on the left side, and a beginner reading the code can be genuinely surprised that a function call was skipped.
It also has a protective use you will meet properly in the arrays and objects module: putting a cheap safety check on the left of an && so that a risky lookup on the right never runs when it would fail. For now, just know that the order of the two sides is not cosmetic.
?? - the fallback that only fires for null and undefined
Explanation
Using || for a default has one flaw, and it is the same flaw as the previous lesson's zero bug. 'savedVolume || 5' means 'use 5 whenever savedVolume is falsy', and a saved volume of 0 is falsy. Someone who deliberately muted the sound gets it turned back up to 5 every time they open the app. That is a real bug that has shipped in real products.
?? is the operator for exactly this. It is called nullish coalescing, and 'nullish' is the word for the two values that mean 'nothing here': null and undefined. 'left ?? right' returns the left side unless the left side is null or undefined, in which case it returns the right side. Nothing else triggers the fallback - not 0, not empty text, not false.
So the choice between them is a question about your data, not about style. Ask: could this value legitimately be 0, empty text, or false? If yes, you want ??. If the only meaningful 'no value here' cases are null and undefined, ?? is still the safer default and || is a shorthand you are choosing knowingly.
MDN — Nullish coalescing operator (??)
Check there: ?? returns its right-hand operand when the left is null or undefined, and otherwise returns the left - unlike ||, which uses the falsy test.
Mixing them: one rule you must obey, one you should
A common mistake
The rule you must obey: ?? cannot be written directly alongside && or || in the same expression. 'a || b ?? c' is not a subtle bug, it is a SyntaxError and the program will not start. The language designers decided that the intended grouping was too easy to misread, so they required you to say it explicitly with brackets: '(a || b) ?? c'. If you hit that error, this is why, and the fix is always brackets.
The rule you should obey: when && and || appear together, && binds more tightly - so 'a || b && c' means 'a || (b && c)', in the same way that 2 + 3 * 4 means 2 + (3 * 4). This is real and reliable, and you should still write the brackets. Nobody reading your code should have to recall an operator precedence table to know what a rule means.
A third mistake, less dramatic but very common: writing 'if (day === "saturday" || "sunday")'. It reads like English and it is always true. The || sees the left comparison (false, say) and returns the right side - the text "sunday" - which is truthy. Each side of an || must be a complete condition on its own: 'day === "saturday" || day === "sunday"'.
Check there: && has higher precedence than ||, and ?? cannot be mixed with && or || without parentheses.
Recap
Recap
&& is true only when both sides are; || when at least one is. Neither returns a boolean: || returns the left side if it is truthy and otherwise the right, && returns the left side if it is falsy and otherwise the right. That means the right side may never be evaluated. ?? falls back only for null and undefined, so it is the correct choice whenever 0, empty text or false are real values. Mixing ?? with && or || without brackets is a SyntaxError.
Worked examples
Every output below was produced by actually running the code in the same sandbox your exercises run in — it is not a prediction.
|| and ?? on the same values
The two operators agree on null and disagree on 0 and empty text. That disagreement is the entire reason ?? exists.
1const savedVolume = 0;2console.log("with ||:", savedVolume || 5);3console.log("with ??:", savedVolume ?? 5);4 5const nickname = "";6console.log("with ||:", nickname || "guest");7console.log("with ??:", "[" + (nickname ?? "guest") + "]");8 9const missing = null;10console.log("with ||:", missing || "guest");11console.log("with ??:", missing ?? "guest");- line 2
0 is falsy, so || decides it is not a real value and hands back 5. The user's deliberate choice of silence has just been overruled.
- line 3
?? only falls back for null and undefined, so 0 survives. This is the correct behaviour for a saved setting.
- line 7
The square brackets are only there so you can see the result: ?? returned the empty text unchanged, so nothing appears between them.
- line 11
When the value really is missing, both operators do the same thing. They only differ on the falsy-but-real values.
What it prints
with ||: 5with ??: 0with ||: guestwith ??: []with ||: guestwith ??: guest
Proving the right-hand side is skipped
shout() prints a line whenever it runs, which makes short-circuiting visible instead of theoretical. Count the 'shout ran' lines in the output.
1function shout(text) {2 console.log("shout ran");3 return text.toUpperCase();4}5 6const enabled = false;7const result = enabled && shout("hello");8console.log(result);9 10const alsoEnabled = true;11console.log(alsoEnabled && shout("hello"));- line 7
enabled is falsy, so && already knows the answer and never evaluates the right-hand side. shout is not called - there is no 'shout ran' line before the next output.
- line 8
And the value stored is false: && returned the left-hand operand itself, not a boolean it computed.
- line 11
Now the left side is truthy, so && has to look at the right to know the answer. shout runs, prints its line, and its return value becomes the value of the whole expression.
What it prints
falseshout ranHELLO
A rule that needs both operators
The rule in English: you may ride if you are at least 140cm, or at least 120cm with an adult. Notice how each side of the && is a complete condition in its own right.
1function canRide(heightCm, hasAdult) {2 if (heightCm >= 140) {3 return true;4 }5 if (heightCm >= 120 && hasAdult) {6 return true;7 }8 return false;9}10 11console.log(canRide(150, false));12console.log(canRide(125, false));13console.log(canRide(125, true));14console.log(canRide(110, true));- line 5
Both sides must hold. hasAdult is already true or false, so it needs no comparison of its own - but heightCm >= 120 does, because a bare 'heightCm' would only ask whether it is non-zero.
- line 14
110cm with an adult is still refused: the && demands both, and the first branch already ruled out the tall-enough-alone case.
What it prints
truefalsetruefalse
Check yourself
Not scored, not stored. Getting one wrong is the useful part.
Four fallbacks, two operators, two left-hand values. Decide each line before you read the output - the last one is the one to think hardest about.
console.log(null || "fallback");console.log(null ?? "fallback");console.log(false || "fallback");console.log(false ?? "fallback");
A saved volume setting can be any number from 0 to 10, where 0 means muted, and it is missing entirely for a new user. Which line supplies a default of 5 correctly?
Why is 'if (day === "saturday" || "sunday")' true for every possible day?
Write it yourself
Exercise · core · 8 tests
Two defaults, two different operators
Write two small functions. volumeFor(saved) returns the saved volume, except when nothing was saved - that is, when saved is null or undefined - in which case it returns 5. A saved volume of 0 means muted and must be returned as 0. titleFor(rawTitle) returns the title, except when the title is missing OR empty text, in which case it returns 'Untitled'; an empty heading is not a heading, so here the falsy test is the one you want. Each function should be a single return line once you have picked the right operator.
volumeFor returns its argument unchanged for any value that is not null or undefined, including 0, and returns 5 for null and undefined. titleFor returns its argument for any non-empty string and returns 'Untitled' for empty text, null and undefined.
Define volumeFor and titleFor at the top level of your code — the tests call them by name.
Worth remembering
- What value does 'a || b' produce?
a itself when a is truthy, otherwise b. It returns one of the operands unchanged - it does not convert anything to a boolean. && is the mirror image: a when a is falsy, otherwise b.
- When is the right-hand side of && or || not evaluated?
When the left side already decides the answer: a falsy left for &&, a truthy left for ||. If the right side calls a function, that call simply does not happen.
- When must you use ?? rather than || for a default?
Whenever 0, empty text or false are legitimate values. ?? falls back only for null and undefined; || falls back for all eight falsy values.
- What happens if you write 'a || b ?? c'?
A SyntaxError - the program does not start. ?? may not sit directly beside && or ||; add brackets to say which grouping you meant.
Check this lesson against the source
We wrote the explanation; we did not invent the facts. This is the page that backs them.
Check there: && returns the first falsy operand, or the last operand if all are truthy, and does not evaluate the right operand when the left is falsy.
Check there: || returns the first truthy operand, or the last operand if none are truthy.
MDN — Nullish coalescing operator (??)
Check there: ?? falls back only for null and undefined, and cannot be combined with && or || without parentheses.
Prefer to read the source in a structured breakdown? Turn this doc into a breakdown