&&, || and ??
One condition answers one question. Real decisions need several at once: the canteen is open and you have money, the user is an admin or the owner of the post, the nickname is missing so fall back to the full name.
Three operators cover all of it, and two of them do something more interesting than they first appear.
&& requires everything
&& is and. It is true only when both sides are true.
let isOpen = true;
let hasMoney = false;
console.log(isOpen && hasMoney);
console.log(isOpen && true);
console.log(false && false);
false true false
You can chain as many as you like, and all of them must hold:
let age = 20;
let hasTicket = true;
let seatsLeft = 3;
console.log(age >= 18 && hasTicket && seatsLeft > 0);
true
|| accepts any one
|| is or. It is true when at least one side is true.
let isAdmin = false;
let isOwner = true;
console.log(isAdmin || isOwner);
console.log(false || false);
console.log(true || false);
true false true
Those two vertical bars are typed with Shift and the backslash key. A single
| is a different operator entirely and will not do what you want.
! flips it
! is not. It turns true into false and back.
let isClosed = false;
console.log(!isClosed);
console.log(!!"chai");
console.log(!"");
true true true
!! is two nots in a row, which converts any value to its true-or-false
equivalent using the truthiness rules from
the if lesson. Boolean(value) says the same thing more
clearly.
Watch out for ! with comparisons. !age > 18 does not mean what it reads
like — it flips age first, then compares false to 18. Write
!(age > 18), or better, age <= 18.
Short-circuiting: they stop early
Here is the part that matters more than the truth tables.
&& stops at the first falsy value. || stops at the first truthy one.
Once the answer is decided, the rest is never evaluated at all.
function shout() {
console.log("shout ran");
return true;
}
console.log(false && shout());
console.log(true || shout());
console.log(true && shout());
false true shout ran true
shout ran once, not three times. In the first line && already knew the
answer was false from the left side, so it never looked right. In the second
|| already had a truthy value. Only the third needed to check.
That is not an optimisation detail — it is a tool. It lets you put a safety check on the left and the risky thing on the right:
let user = null;
console.log(user && user.name);
null
user is falsy, so user.name is never evaluated, and the crash that would
have caused never happens.
They return a value, not just true or false
This surprises people who learnt logic first. && and || do not return
booleans — they return one of the two operands.
console.log("chai" && "paratha");
console.log(0 && "paratha");
console.log("chai" || "paratha");
console.log(0 || "paratha");
console.log(null || 0 || "" || "last");
paratha 0 chai paratha last
&& returns the first falsy value, or the last value if none are falsy.
|| returns the first truthy value, or the last value if none are.
That is what makes || usable as a default:
let nickname = "";
let fullName = "Ayesha Siddiqui";
console.log(nickname || fullName);
Ayesha Siddiqui
The nickname is empty, which is falsy, so the full name is used. Very tidy — and it is also the setup for the bug in the next section.
The || default eats your zeros
You want a quantity to default to 1 when nothing was chosen, so you write
quantity || 1. It reads like "quantity, or 1 if there isn't one". It is not.
let chosenQuantity = 0;
let quantity = chosenQuantity || 1;
console.log(quantity);
1
The customer chose zero. Zero is falsy. So || skipped it and substituted 1,
and the order goes out with one item nobody asked for.
Test it with 3 and it works. Test it with 5 and it works. The only value that
breaks is the one that looks most like "nothing" while actually being a real
answer — and the same applies to an empty string in a comment field, or
false in a checkbox setting.
|| does not ask "is this missing?". It asks "is this falsy?" Those are
different questions whenever 0, "" or false are legitimate data.
?? asks the question you meant
The nullish coalescing operator ?? falls back only when the left side is
null or undefined. Everything else — including zero, empty string and
false — is kept.
let chosenQuantity = 0;
console.log(chosenQuantity || 1);
console.log(chosenQuantity ?? 1);
1 0
Same expression, two operators, opposite answers, and ?? is the one that
respects what the customer typed.
Compare across the falsy values:
console.log("" || "fallback");
console.log("" ?? "fallback");
console.log(false || "fallback");
console.log(false ?? "fallback");
console.log(null ?? "fallback");
console.log(undefined ?? "fallback");
fallback fallback false fallback fallback
The second line prints an empty string, which is why it looks like a blank line. That is the point: the empty string survived.
The working rule: use ?? for defaults, and || only when you genuinely
mean "any falsy value should be replaced" — which is rarer than it sounds.
One syntax note. You cannot mix ?? with && or || without brackets:
let a = null;
let b = "b";
console.log((a ?? "x") || b);
x
Writing a ?? "x" || b is a syntax error, deliberately, because the
precedence would be ambiguous to a reader. The brackets are required and they
make the intent obvious.
Optional chaining, while you are here
?. is the same idea applied to reading a property. It stops and returns
undefined rather than throwing when the thing on the left is null or
undefined.
let student = { name: "Bilal" };
let missing = null;
console.log(student?.name);
console.log(missing?.name);
Bilal undefined
Without it, missing.name throws and stops the program. ?. and ?? pair
naturally: missing?.name ?? "Unknown" reads as "the name if there is one,
otherwise Unknown".
Combining conditions readably
let hour = 14;
let isWeekend = false;
let staffOnSite = true;
let canOrder = hour >= 8 && hour < 17 && (!isWeekend || staffOnSite);
console.log(canOrder);
true
Brackets are not required by the grammar here — && binds tighter than || —
but they are required by the next person to read it. When a condition takes
more than a moment to parse, give it a name:
let hour = 14;
let isWeekend = false;
let staffOnSite = true;
let withinHours = hour >= 8 && hour < 17;
let someoneToServe = !isWeekend || staffOnSite;
console.log(withinHours && someoneToServe);
true
Two named booleans, one obvious final line. That is almost always worth the extra two lines, and it makes the failing case easy to print when the answer is not what you expected.
Next: loops, which repeat a decision like this one over every item in a list.
Prefer learning with a teacher?
WasiLearn Academy runs small-group classes covering this material, online and in Karachi.
See the classes