WasiLearn Code
JavaScript · Making decisions and repeating

&&, || 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);
output
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);
output
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);
output
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(!"");
output
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());
output
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);
output
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");
output
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);
output
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);
output
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);
output
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");
output
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);
output
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);
output
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);
output
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);
output
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