Logical operators
Learn JavaScript's &&, ||, and ! operators: truthy and falsy decisions, short-circuit evaluation, operand return values, guard patterns, and safe defaults.
- 01Read logical checksPredict what &&, ||, and ! give for truthy and falsy values.
- 02Spot skipped codeKnow when JavaScript stops early and skips the right side.
- 03Use guards and defaultsWrite them without being surprised by 0, '', null, or undefined.
- 04Name checks clearlyRead a check with several parts out loud, and pick an if statement when the visitor needs to be told what is missing.
Three operators, one idea
Code often needs to ask small yes-or-no questions. For example: "Do we have both things we need?" "Do we have at least one value we can use?" "Is this not true?" JavaScript has three logical operators for these questions:
&&means "and".||means "or".!means "not".
They use truthy and falsy values from Booleans & truthiness. A truthy value counts as "yes" in a check. A falsy value counts as "no". There are exactly eight falsy values:
false,0,-0, and0n"", the empty stringnull,undefined, andNaN
Every other value is truthy. So "Ada", 10, and even an empty list [] are truthy.
The values on each side of an operator are called its operands. In a && b, this lesson calls a the left value and b the right value.
A logical operator checks whether a value is truthy or falsy. && and || can stop early and skip the right value. They give back one of the two values itself. Only ! always gives back true or false.
Picture a door that needs a badge and a PIN. If you have no badge, the guard does not ask for your PIN. The missing badge already decides the answer: you can't go in.
- In real life: The guard asks for your badge
- In JavaScript: JavaScript checks the left value of
&& - In real life: Then the guard asks for your PIN
- In JavaScript: JavaScript checks the right value of
&& - In real life: No badge? The guard stops right away
- In JavaScript: A falsy left value stops
&&early - In real life: Badge and PIN? You can go in
- In JavaScript: Both values must be truthy
In this lesson you will:
- step through code where a function call is skipped,
- try different values and see exactly what comes back,
- use two common patterns, called guards and defaults,
- see why
0 || 10gives10, and how the next lesson's??fixes it.
&&, ||, and !
TABLESStart with plain true and false. Then the three operators are easy to read:
a && bis true only when both are true.a || bis true when at least one is true.!aflips the value: true becomes false, and false becomes true.
The table below shows the same rules with examples. Read each row as: "this code gives true when …".
| When you write | When it gives true | Examples |
|---|---|---|
a && b | Only when a and b are both true. | true && true → true; true && false → false |
a || b | When a is true, or b is true, or both are. | false || true → true; false || false → false |
!a | When a is falsy. | !false → true; !true → false |
Real code often uses values that are not true or false, like names and numbers. Then JavaScript does two things:
- It checks whether the value is truthy or falsy, to decide what to do.
- It gives back the value itself, not
trueorfalse.
That is why "cat" && 10 gives 10, not true.
&& and || check whether each value is truthy or falsy. Then they give back one of the two values themselves. Here both give back the right value: 10 and "Guest", not true.Boolean(value) is a different tool. It turns any value into true or false. The next table compares it with the operators. Read each row from left to right: the code, what JavaScript checks, and what you get back.
| When you write | What JavaScript checks | What you get back |
|---|---|---|
Boolean("cat") | The string is truthy. | true, because Boolean() always gives true or false. |
"cat" && 10 | The left value is truthy, so it moves on to the right value. | 10, the right value itself. |
0 || "Guest" | The left value is falsy, so it moves on to the right value. | "Guest", the right value itself. |
!"cat" | The string is truthy, and ! flips that. | false, because ! always gives true or false. |
const hasTicket = true;const hasId = false; console.log(hasTicket && hasId);console.log(hasTicket || hasId);console.log(!hasId);Line 1 says the visitor has a ticket. Line 2 says they have no ID. Then:
- Line 4 prints
false.&&needs both checks to pass, and the ID check fails. - Line 5 prints
true.||needs only one check to pass, and the ticket check passes. - Line 6 prints
true.!flipshasId, which isfalse.
Give true-or-false variables names that sound like a question, such as hasTicket and isSignedIn. Then you can read hasTicket && hasId out loud: "has a ticket and has an ID". Clear names make a check much easier to get right.
console.log("Asha" && "ready");console.log(0 && "ready");console.log("Asha" || "Guest");console.log("" || "Guest");console.log(!"tea");console.log(!!"tea");Line by line:
"Asha"is truthy, so&&gives back the right value. It printsready.0is falsy, so&&stops and gives back0."Asha"is truthy, so||stops and gives back"Asha".""is falsy, so||moves on and gives back"Guest"."tea"is truthy, and!flips it, so this printsfalse.!!flips twice, so it printstrue. Two flips turn any value intotrueorfalse.
Notice the pattern. && and || work the opposite way round. && stops at a falsy value. || stops at a truthy value.
console.log(Boolean([]));console.log([] && "tea");console.log([] || "Guest");console.log("0" && 3);console.log(NaN || 5);Line by line:
- An empty list,
[], is still truthy. SoBoolean([])printstrue. - Because
[]is truthy,&&moves on and printstea. - Because
[]is truthy,||stops and gives back the list itself, not"Guest". - The string
"0"has one character in it, so it is truthy.&&moves on and prints3. NaNis falsy, so||moves on and prints5.
Often you want the value itself, as in name || "Guest". Use Boolean(value) or !!value only when you need exactly true or false, for example to store a yes-or-no answer in a variable.
Short-circuit: stopping early
STEP THROUGHShort-circuit means JavaScript stops as soon as it knows the answer. Then the right value is never worked out at all.
- For
&&, a falsy left value already means "no". JavaScript stops. - For
||, a truthy left value already means "yes". JavaScript stops.
This matters when the right side does something, like call a function, read a property, or print a message. If JavaScript stops early, that never happens.
Picture trying keys on a keyring. The first key that opens the lock wins. Once the door is open, you don't try the other keys.
- In real life: Try the front-door key
- In JavaScript: JavaScript checks the left value
- In real life: It opens the door? Stop trying keys
- In JavaScript: A truthy left value stops
||early - In real life: It doesn't open? Try the next key
- In JavaScript: A falsy left value makes JavaScript check the right value
Predict which function runs. Then step through the real run: a skipped function has no step at all.
script
function checkBadge() { console.log("badge checked"); return "";}function checkPin() { console.log("PIN checked"); return "pin-ok";const result = checkBadge() && checkPin();console.log(result);Look at the function that was skipped. It printed nothing, gave back nothing, and has no step in the replay. JavaScript really didn't run it. That's why isLoggedIn && showDashboard() calls showDashboard() only when isLoggedIn is truthy.
let savedTimes = 0;function saveCart() { savedTimes += 1; console.log("cart saved");} false && saveCart();true && saveCart();console.log(savedTimes);Lines 1 to 5 set up a counter, savedTimes, and a saveCart() function. Each call adds 1 to the counter (+= 1 means "add 1"). Then:
- Line 7 does not call
saveCart(), because the left value isfalse. - Line 8 does call it, because the left value is
true. - Line 9 prints
1. That shows only one save happened.
&& looks at the left value first. When it is falsy, JavaScript stops there and never calls saveCart(). When it is truthy, JavaScript goes on to the right and the call runs.So never put a step that must always happen on the right side of && or ||. It may be skipped.
"badge" && checkPin()0 && saveScore()"Ada" || "Guest""" || "Guest"user && user.namenull && user.name
Sort each line: does JavaScript work out the right value, or skip it?
They give back a value, not true or false
INTERACTIVEThis rule surprises many beginners. && and || give back the value that decided the answer. They give back true or false only when that value already was true or false. Experienced developers use this rule to write short code. Here are the exact rules:
a && bgives backaifais falsy. Otherwise it gives backb.a || bgives backaifais truthy. Otherwise it gives backb.!aalways gives backtrueorfalse.!!aflips twice, so it is a common way to turn any value into exactlytrueorfalse.
In the playground below, pick a left value, an operator, and a right value. Line 3 of the code prints what you get back. Line 4 prints the same answer turned into true or false.
const left = "cat";const right = 10;console.log(left && right);console.log(Boolean(left && right));console.log(!left);console.log(!!left);- Value given back
10- Boolean(result)
true- !left
false- !!left
true
The line gives back 10. Boolean(result) is true. !left is false, and !!left is true.
Which operator goes first
STEP THROUGHWhen one line mixes several operators and has no brackets, JavaScript needs a rule for which part to work out first. That rule is called precedence. For the logical operators, the order is:
!goes first.- Then
&&. - Then
||.
So JavaScript reads true || false && false as true || (false && false). That gives true.
&& goes before ||, so JavaScript reads true || false && false as if the && part had brackets around it. Step 1 gives false. Step 2, true || false, gives true.Step through the four lines to see which operator goes first, and one example of De Morgan's law.
script
console.log(true || false && false);console.log((true || false) && false);console.log(!(true && false) === (!true || !false));Brackets are often better than remembering the order. They show the next reader exactly what you meant. That reader may be you, a few months from now.
console.log(!false && false);console.log(true || false && false);console.log((true || false) && false);console.log(!(true && false) === (!true || !false));Line by line:
!goes first, so!falsebecomestrue. Thentrue && falsegivesfalse.&&goes before||, so this istrue || (false && false). It givestrue.- The brackets change the order.
(true || false)istrue, thentrue && falsegivesfalse. - Both sides of
===givetrue, so the line printstrue. The next paragraph explains why.
Line 4 checks a rule called De Morgan's law. It shows how to move a ! inside brackets: !(a && b) means the same as !a || !b. In words: "not both passed" means the same as "the first failed, or the second failed".
Guards and defaults
INTERACTIVEStopping early gives you two useful patterns.
- A guard goes on only when the left value is safe to use.
user && user.namereadsnameonly whenuserexists. Ifuserisnull, it stops, so it never tries to readnamefromnull. That read would throw an error. - A default is a backup value.
name || "Guest"gives"Guest"whennameis falsy.
const user = { name: "Ada" };const missingUser = null;console.log(user && user.name);console.log(missingUser && missingUser.name);console.log("" || "Guest");console.log(0 || 10);- user && user.name
null- name || "Guest"
"Guest"- count || 10
10- count ?? 10
0
user && user.name gives null. name || "Guest" gives "Guest". count || 10 gives 10. The next lesson's count ?? 10 would give 0.
Defaults with || have one catch. || treats every falsy value as missing. But sometimes 0 is a real value, like a count of 0 items. Then 0 || 10 wrongly gives 10.
The next lesson, Nullish coalescing & optional chaining, teaches two fixes:
??uses the backup only when the value isnullorundefined. So0 ?? 10keeps0.?.replaces long guard chains likeuser && user.address && user.address.city.
count holds a real 0. || treats every falsy value as missing, so it swaps the 0 for 10. ??, from the next lesson, only swaps null and undefined, so the 0 stays.const user = { name: "Ada" };const missingUser = null;console.log(user && user.name);console.log(missingUser && missingUser.name);console.log("" || "Guest");console.log(0 || 10);Lines 3 to 6, one at a time:
- Line 3 prints
Ada.useris an object, so it is truthy and the guard goes on to read the name. - Line 4 prints
null.missingUserisnull, so the guard stops before reading.name. - Line 5 prints
Guest. The empty string is falsy, so the backup is used. - Line 6 prints
10. This is the catch:0is falsy, so it is replaced too.
Where you'll use this
You'll use logical operators whenever code needs a small decision inside one line. Here are common examples:
isLoggedIn && showDashboard()calls a function only when a check is truthy.const label = nickname || "Guest";shows a backup name when the nickname is empty.const city = user && user.address && user.address.city;is an older guard chain. The next lesson replaces it withuser?.address?.city.if (isAdmin && hasToken) { ... }puts the check in anifstatement, which is often easier to read.
A call after && is handy for one tiny action. If the action needs several lines, an if statement is clearer and easier to debug. A later module covers if statements in full.
const name = " Asha ";const acceptedTerms = true;const hasName = Boolean(name.trim()); console.log(hasName && acceptedTerms);Line 3 uses name.trim(), which removes spaces from both ends of the text. What is left is "Asha". Boolean() then turns it into true, because it is not empty. Line 5 prints true, because the name has letters and the terms were accepted.
This only checks that the form is ready to send. It does not check everything. An email address, a password, and the server's own rules each need their own checks.
Step through the checkout check. The replay follows this exact example step by step; it doesn't show what happens inside the JavaScript engine.
script
function showCheckout() { console.log("checkout shown"); return "checkout";} const isSignedIn = true;const hasItems = true;console.log(page);The checkout example uses two true-or-false variables, so its result is easy to predict. Lines 6 and 7 say the visitor is signed in and has items in their cart. Line 8 reaches showCheckout() only after both checks pass. Line 9 prints checkout. If either variable were false, the function would be skipped and the line would give back false.
const isSignedIn = true;const hasAddress = true;const hasItems = false; const canPlaceOrder = isSignedIn && hasAddress && hasItems;console.log(canPlaceOrder);Lines 1 to 3 hold three yes-or-no answers, and each name reads like a question. Line 5 reads out loud as "is signed in and has an address and has items". The last value, hasItems, is false. So the whole && line gives back false, and line 6 prints false.
On a real order page, you could use a check like this to turn the order button on or off. If you also need to tell the visitor what is missing, use an if statement.
const nickname = "";const cartCount = 0; console.log(nickname || "Guest");console.log(cartCount || 10);console.log(cartCount ?? 10);- Line 4 prints
Guest, because the nickname is empty. - Line 5 shows the catch.
0is falsy, socartCount || 10prints10. - Line 6 uses
??. It keeps the real count, so it prints0.
Use ??, from the next lesson, whenever 0 or an empty string is a real value you want to keep.
Common misconceptions
- "Logical operators always give true or false." Only
!does.&&and||give back one of their two values. - "The right side always runs." JavaScript may stop early and skip it completely.
- "A falsy value is useless."
0and""can be real data. Be careful with||defaults. - "
!!xis the same asx." Both are truthy or both are falsy. But!!xis always exactlytrueorfalse. - "The order is obvious." Lines that mix
&&and||are easy to misread. Add brackets to make the order clear.
Practice exercises
6 EXERCISESPredict all three printed values before you run the code.
console.log("cat" && 10);
console.log(0 || "fallback");
console.log(!"");console.log("cat" && 10);
console.log(0 || "fallback");
console.log(!"");The string "cat" is truthy, so the first line prints 10. 0 is falsy, so the second line prints fallback. The empty string is falsy, and ! flips it, so !"" is true.
Find the surprising printed value. Then look at the solution to see the fixed default.
const count = 0;
console.log(count || 10);const count = 0;
console.log(count ?? 10);count || 10 prints 10, because 0 is falsy. Write count ?? 10 when only null or undefined should count as missing.
Use && so the code reads name only when user exists.
const user = null;
console.log(user && user.name);const user = null;
console.log(user && user.name);user && user.name gives back null when user is null. JavaScript skips the right side, so it never tries to read .name from null, and there is no error.
This one line does the same job as an if inside another if. Follow the && chain from left to right and decide whether the function call runs.
const isLoggedIn = true;
const hasPlan = true;
function showDashboard() { console.log("dashboard"); }
isLoggedIn && hasPlan && showDashboard();const isLoggedIn = true;
const hasPlan = true;
function showDashboard() { console.log("dashboard"); }
isLoggedIn && hasPlan && showDashboard();Both checks are true, so JavaScript reaches showDashboard(), which prints dashboard. For longer work, an if statement would be clearer.
Write the same check without the outer ! and brackets. It must give the same answer.
const hasBadge = true;
const hasPin = false;
console.log(!(hasBadge && hasPin));
console.log(!hasBadge || !hasPin);!hasBadge || !hasPin!(hasBadge && hasPin) means at least one is missing. De Morgan's law writes that as !hasBadge || !hasPin. With hasBadge true and hasPin false, both lines print true.
A shop should open checkout only for a visitor who is signed in and has items in the cart. Predict whether the function runs in this code. Then change the first line to true and run it again to see checkout open.
const isSignedIn = false;
const hasItems = true;
function showCheckout() { console.log("checkout shown"); }
isSignedIn && hasItems && showCheckout();const isSignedIn = false;
const hasItems = true;
function showCheckout() { console.log("checkout shown"); }
isSignedIn && hasItems && showCheckout();isSignedIn is false, so the && chain stops right away. JavaScript doesn't look at hasItems, and showCheckout() never runs. This works for one tiny action. Use an if statement when the visitor needs a message, or the work takes several lines.
Check your understanding
8 QUESTIONSQuestion 1 of 8What does this line print?
Read the code, then predictconsole.log("cat" && "dog");Choose an answer to see the explanation.
Question 2 of 8Which line skips the function call?
Choose an answer to see the explanation.
Question 3 of 8What does this default print when the name is empty?
Read the code, then predictconsole.log("" || "Guest");Choose an answer to see the explanation.
Question 4 of 8What does !0 print?
Read the code, then predictconsole.log(!0);Choose an answer to see the explanation.
Question 5 of 8How does JavaScript read
true || false && false?Choose an answer to see the explanation.
Question 6 of 8
countcan be 0, and 0 should be kept. Which fix from the next lesson replacescount || 10?Choose an answer to see the explanation.
Question 7 of 8Which line means the same as
!(hasBadge && hasPin)?Choose an answer to see the explanation.
Question 8 of 8How many times does saveCart run?
Read the code, then predictlet savedTimes = 0; function saveCart() { savedTimes += 1; } false && saveCart(); true && saveCart(); console.log(savedTimes);Choose an answer to see the explanation.
Key takeaways
&&needs both values to be truthy. It stops at the first falsy value.||needs one truthy value. It stops at the first truthy value.!flips truthy and falsy, and always givestrueorfalse.&&and||give back one of their values, nottrueorfalse.- Use guards and defaults with care.
0and""are falsy, but they are often real data.
Logical operators check truthy and falsy values from left to right, stop as soon as they know the answer, and give back the value that decided it.
Coming next: Nullish coalescing & optional chaining.