Logical Operators, Ternary, ?? and ?.

Combine conditions with &&, || and !, write short if/else with the ternary operator, and handle missing values safely with ?? and optional chaining.

Lesson 11 of 56Beginner16 min read

Real decisions rarely depend on a single question. A discount applies if the customer is a member and the basket is over £50. A form can be sent if the email is filled in or the phone number is. A button should show unless the user is already subscribed. JavaScript's logical operators, && (and), || (or) and ! (not), combine comparisons like these into one condition.

These operators have a second, very practical use. Because of something called short-circuit evaluation, they can pick between values, which is how JavaScript developers write default values and conditional actions in a single line.

This lesson also covers three short pieces of syntax you'll see in almost every modern codebase: the ternary operator (condition ? a : b) for compact if/else choices, the nullish coalescing operator (??) for safe defaults, and optional chaining (?.) for reading properties that might not exist without crashing your program.

And, or, not

OperatorNameResult is true when…Example
&&ANDboth sides are trueage >= 18 && hasTicket
||ORat least one side is trueisAdmin || isOwner
!NOTthe value is false!isLoggedIn
JavaScript
const age = 20;
const hasTicket = true;
console.log(age >= 18 && hasTicket); // true
console.log(age < 12 || age > 65);   // false
console.log(!hasTicket);             // false

&& is evaluated before ||, just as multiplication comes before addition. Use brackets to make mixed conditions clear: (a && b) || c.

Short-circuit evaluation

JavaScript stops checking as soon as it knows the answer, and it returns the value that decided it, not always true or false:

  • a || b returns a if it's truthy, otherwise b.
  • a && b returns a if it's falsy, otherwise b.
JavaScript
const name = "" || "Guest";       // "Guest"
const user = { name: "Sam" };
user && console.log(user.name);  // logs "Sam"

This is why || is often used for default values. The catch: || treats 0, "" and false as missing too, which isn't always what you want.

Nullish coalescing: ??

?? gives a default only when the left side is null or undefined:

Expression|| result?? result
0 ?? 10 vs 0 || 10100
"" ?? "N/A" vs "" || "N/A""N/A"""
null ?? 5 vs null || 555
undefined ?? 5 vs undefined || 555

Use ?? for settings such as volume, quantity or scores, where 0 is a real value. The assignment versions x ??= 5, x ||= 5 and x &&= y update a variable only when needed.

The ternary operator and optional chaining

The ternary operator is a one-line if/else that produces a value:

JavaScript
const label = stock > 0 ? "In stock" : "Sold out";

Optional chaining ?. reads a property only if the thing before it exists. If it's null or undefined, the whole expression returns undefined instead of throwing an error:

JavaScript
const city = user.address?.city;      // undefined if there's no address
const first = order.items?.[0];       // safe array access
user.notify?.();                      // only calls notify if it exists

Combine them: user.address?.city ?? "Unknown" reads safely and falls back to a default.

JavaScript Logical Operators Syntax

Logical operators

if (isMember && total > 50) { /* ... */ }
if (email || phone) { /* ... */ }
if (!done) { /* ... */ }

const volume = settings.volume ?? 50;
const label = count === 1 ? "item" : "items";
const city = user?.address?.city ?? "Unknown";
count ||= 1;   // set if falsy
name ??= "Anon"; // set if null or undefined

JavaScript Logical Operators Summary

  • && needs both sides true, || needs one, and ! flips a value.
  • Logical operators short-circuit and return the deciding value.
  • ?? provides a default only for null and undefined, so 0 and "" are kept.
  • condition ? a : b chooses between two values in one line.
  • ?. reads properties safely when something might be missing.

JavaScript Logical Operators Examples

Each example runs in the page. Edit the code and press Run to see what changes.

Example 1

Combining conditions

Change the values on lines 1 to 3 and run again.

const age = 16;
const withAdult = true;
const isMember = false;

const canEnter = age >= 18 || withAdult;
const getsDiscount = isMember && age < 18;
const needsId = !isMember;

console.log("Can enter:", canEnter);
console.log("Gets discount:", getsDiscount);
console.log("Needs ID:", needsId);
console.log("Rule:", (age >= 18 || withAdult) && !needsId);
ConsoleEdit the code, then press Run or Ctrl+Enter
Line-by-line explanation
  1. Line 5True if the visitor is an adult or comes with one.
  2. Line 6True only if both conditions are true.
  3. Line 7! flips false to true.
  4. Line 12Brackets make the order of a mixed condition clear.
Example 2

|| vs ?? for defaults

const settings = { volume: 0, nickname: "", theme: null };

const v1 = settings.volume || 50;
const v2 = settings.volume ?? 50;
console.log("volume with ||:", v1);
console.log("volume with ??:", v2);

const nick = settings.nickname ?? "Player";
const theme = settings.theme ?? "light";
console.log(`nickname: "${nick}"`, "theme:", theme);

let tries;
tries ??= 3;
console.log("tries:", tries);
ConsoleEdit the code, then press Run or Ctrl+Enter
Line-by-line explanation
  1. Line 3|| replaces the valid value 0 with 50, which is a bug.
  2. Line 4?? keeps 0 because it's not null or undefined.
  3. Line 8The empty string is kept too.
  4. Line 9null gets the default.
  5. Line 13??= assigns only because tries is undefined.
Example 3

Ternary and optional chaining with real data

const users = [
  { name: "Ana", address: { city: "Lisbon" }, cart: ["book", "pen"] },
  { name: "Ben" },
];

for (const user of users) {
  const city = user.address?.city ?? "unknown city";
  const count = user.cart?.length ?? 0;
  const word = count === 1 ? "item" : "items";
  console.log(`${user.name} from ${city} has ${count} ${word}`);
}
ConsoleEdit the code, then press Run or Ctrl+Enter
Line-by-line explanation
  1. Line 7Ben has no address, so ?. returns undefined and ?? supplies a default.
  2. Line 8The same pattern for a missing cart.
  3. Line 9The ternary picks the right word for the number.

Common JavaScript Logical Operators Mistakes

Using || when 0 is valid

Don't
const qty = input.quantity || 1; // a quantity of 0 becomes 1
Do
const qty = input.quantity ?? 1;

Comparing against several values with ||

Don't
if (day === "Sat" || "Sun") { } // always true: "Sun" is truthy
Do
if (day === "Sat" || day === "Sun") { }
// or
if (["Sat", "Sun"].includes(day)) { }

Optional chaining on the left of =

Don't
user?.profile.name = "Sam"; // SyntaxError
Do
if (user?.profile) user.profile.name = "Sam";

?. is for reading, not assigning.

JavaScript Logical Operators in Real Projects

Where you'll use these

Form validation combines checks with &&. Feature flags use || to enable something for admins or beta users. React and other frameworks use && and the ternary operator to show or hide parts of the page. Data from APIs often has missing fields, so ?. and ?? appear everywhere data is displayed, for example product.reviews?.length ?? 0.

JavaScript Logical Operators Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Give size a default of 12 only if fontSize is null or undefined.

const fontSize = null;
const size = fontSize  12;
console.log(size);
Predict the outputmedium

What is logged?

console.log(0 || "a", 0 ?? "b", "" && "c");
Multiple choiceeasy

Which condition is true only when both values are true?

Multiple choicemedium

What does user.settings?.theme return if settings is undefined?

Fix the bugmedium

describe should return "no charge" for children under 5 or adults over 65, keep a discount of 0, and not crash when pass is missing. Fix the three bugs.

function describe(age, pass) {
  const discount = pass.discount || 10;
  const exempt = age < 5 && age > 65;
  return exempt ? "no charge" : `pay, discount ${discount}`;
}
console.log(describe(3));
console.log(describe(30, { discount: 0 }));
ConsoleEdit the code, then press Run or Ctrl+Enter
Checks
  • Young children pay nothing
  • Over 65s pay nothing
  • A discount of 0 is kept
  • No pass gives the default

JavaScript Logical Operators Quiz

5 questions to check what you learned.

  1. What does !true return?

    • true
    • null
    • An error
    • false
  2. What does null || "x" return?

    • "x"
    • null
    • true
    • false
  3. When should you use ?? instead of ||?

    • Always
    • Never
    • Only with strings
    • When 0, "" or false are valid values that shouldn't be replaced
  4. What does x > 10 ? "big" : "small" return when x is 4?

    • "big"
    • true
    • "small"
    • 4
  5. What does false && expensiveCheck() do?

    • Runs expensiveCheck and returns false
    • Returns false without calling expensiveCheck
    • Throws an error
    • Returns undefined

JavaScript Logical Operators Challenge

Shipping label

Write shippingLabel(order) that returns a string:

  1. If order.total >= 50 and order.country === "UK", the cost is 0, otherwise 4.99
  2. The city comes from order.address?.city, defaulting to "Unknown" with ??
  3. Return `${city}: ${cost === 0 ? "Delivery included" : "£" + cost}`
function shippingLabel(order) {

}

console.log(shippingLabel({ total: 60, country: "UK", address: { city: "Leeds" } }));
console.log(shippingLabel({ total: 20, country: "UK" }));
ConsoleEdit the code, then press Run or Ctrl+Enter
Checks
  • Big UK orders ship at no cost
  • Small orders pay
  • Other countries pay
  • Missing address is handled