Command Palette

Search for a command to run...

Hectal
PHASE 2Beginner ~25 min· topic 2 of 7

Topic 2.2

Logical Operators

In one line

&&, || and ! combine true/false conditions, but && and || really return one of their operands and stop early (short-circuit); ?? picks a fallback only for null or undefined, and ||=, &&= and ??= assign on the same rules.

Think of it like this

Getting into a cinema. You need a ticket and to be on time: if you have no ticket, nobody checks your watch. You can pay by cash or card: if you have cash, nobody asks for your card. And a sign saying "seat: not assigned yet, use any free seat" is like a fallback that only applies when there is no seat number at all, not when your seat is number 0 in the front row.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Logical operator
An operator that combines or flips conditions: && (and), || (or), ! (not).
Truthy / falsy
Whether a value counts as true or false in a condition. The falsy ones are false, 0, -0, 0n, "", null, undefined and NaN; everything else is truthy.
Short-circuit
Stopping early: the right side of &&, || or ?? isn't run when the left side already decides the answer.
Nullish
Exactly null or undefined, the two "no value" values.
Nullish coalescing (??)
An operator that gives a fallback only when the left side is null or undefined.
Default value
The value you use when the real one is missing.
Logical assignment
||=, &&= and ??=: assign only when the left side is falsy, truthy or nullish.

Step by step

01Combine conditions

Most of the time you use logical operators inside an if: "logged in and has paid", "is admin or is owner", "not empty".

access.jswhole filejavascript
const loggedIn = true;
const hasPaid = false;
console.log(loggedIn && hasPaid); // false
console.log(loggedIn || hasPaid); // true
console.log(!hasPaid);            // true

02Notice they return an operand

|| gives back the first truthy value it finds (or the last value if none is truthy). && gives back the first falsy value (or the last value if all are truthy). Read them as "find the first value that decides".

Notice they return an operanddiagram
Rendering diagram…

03Use short-circuiting as a guard

Because the right side is skipped, && can protect it: cart && cart.items.length won't crash when cart is null. || and ?? can provide a fallback: name || "Guest".

04Pick || or ?? for defaults

Ask: "is 0, "" or false a real answer here?" If yes, use ??, which only replaces null and undefined. If no, || is fine.

defaults.jswhole filejavascript
const volume = 0;           // the user muted it
console.log(volume || 50); // 50  (wrong: un-muted!)
console.log(volume ?? 50); // 0   (right)

05Fill in missing values with ??=

Logical assignment updates a variable or property in place, only when needed. It's the shortest correct way to set defaults on an options object.

options.jswhole filejavascript
const options = { retries: 0 };
options.retries ??= 3;  // stays 0
options.timeout ??= 5000; // added: was undefined

Try it yourself

  1. 1

    Predict the operand

    In the first example, add console.log("cat" && "dog", "cat" || "dog");. Predict both before running. (dog cat.)

  2. 2

    Make the guard fail

    In the second example, change const cart = null; to const cart = { items: [1, 2] };. What does the last line print now? (2.)

  3. 3

    Swap the operators

    In the third example, change the page's volume line to use || instead of ??. The page now says 50 even though the user muted it. Switch it back.

Code & diagrams

&&, || and ! return values New tab

`||` returns the first truthy operand, `&&` the first falsy one (or the last operand if none decides). Only `!` always returns a boolean.

Console

(nothing was printed)

Expected console output

false true false
Asha
Guest
0
yes
null
found
true false true false
Short-circuiting: the right side may never run New tab

Without the `cart &&` guard, `cart.items` would throw a TypeError. `?.` (ES2020) does the same job more neatly: `cart?.items.length`.

Console

(nothing was printed)

Expected console output

checked a
checked c
checked e
checked f
null
?? vs || for defaults ES2020+ New tab

A volume of `0` is a real setting, so `??` is right. For the display name, an empty string should fall back, so `||` is right there.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

volume || 50
volume ?? 0
name || 'Anonymous'
name ?? ''
theme ?? light
missing ?? 16
Logical assignment: ||=, &&= and ??= ES2021+ New tab

Console

(nothing was printed)

Expected console output

{ retries: 3, label: '', user: 'guest', timeout: 5000 }
ABC 0

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Mix ?? with || without parentheses

In the third example, add console.log(null || undefined ?? "x");.

terminal
$ Console
── what you'll see ──
Uncaught SyntaxError: Unexpected token '??'

Break #2

Use || where 0 is a valid value

In the third example, set volume: 0 and display it with settings.volume || 50.

terminal
$ Page
── what you'll see ──
Volume: 50 (the user had muted it)

Myth vs fact

Myth

&& and || always return true or false.

Fact

They return one of their operands unchanged. Only ! (and Boolean()) always produce a boolean.

Myth

?? and || are interchangeable.

Fact

|| replaces every falsy value (0, "", false, NaN...). ?? replaces only null and undefined. For defaults where 0 or "" are valid, only ?? is correct.

Myth

a ||= b is exactly a = a || b.

Fact

When a is already truthy, ||= doesn't assign at all, so no setter runs and assigning to a const holding a truthy value doesn't throw. a = a || b always assigns.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    In the spec, &&, || and ?? are not operators that evaluate both sides; they are control flow. That's why they can't be overloaded or called as functions, and why minifiers turn if (a) b(); into a && b();. Prefer the if in source code: it's clearer and the minifier will do the rest.

  • ▸

    ?? was designed with a deliberate SyntaxError when mixed with &&/||, so that the meaning of a ?? b || c never depends on a precedence rule nobody remembers. TypeScript and linters enforce the same.

  • ▸

    React and other JSX code often write {count && <List />}. When count is 0, && returns 0 and React renders the digit 0. Use {count > 0 && <List />} instead: a classic bug caused by && returning an operand (the React course at /react covers it in Topic 1.4).

  • ▸

    Short-circuiting is also used for lazy evaluation of expensive work: cache.get(key) ?? compute(key) only computes on a miss. That's memoisation in miniature (Topic 15.3).

Remember this

  1. 1

    **&& (AND) means "both". || (OR) means "at least one". ! (NOT)** flips a value: !true is false. With real booleans they work like a truth table: true && false is false, true || false is true. ! always returns a boolean, after converting its operand by the truthy/falsy rules (Topic 1.6): !0 is true, !"hi" is false. Two of them, !!value, is a common way to turn any value into its boolean, the same as Boolean(value).

  2. 2

    Here is what most tutorials skip: && and || don't return true or false. They return one of their operands, unchanged. a || b returns a if a is truthy, otherwise b. a && b returns a if a is falsy, otherwise b. So "Asha" || "Guest" is "Asha", "" || "Guest" is "Guest", and 0 && "yes" is 0. In an if this makes no difference (the result is checked for truthiness anyway), but when you store the result it matters a lot.

  3. 3

    Short-circuiting means the right side is only evaluated when it's needed. In a && b, if a is falsy the answer is already known, so b never runs. In a || b, if a is truthy, b never runs. Programs use this on purpose: user && user.name reads name only if there is a user, and isReady || start() calls start() only if not ready. Any function call or assignment on the skipped side simply doesn't happen.

  4. 4

    **?? (nullish coalescing, since ES2020) returns the right side only when the left side is nullish**: null or undefined. Unlike ||, it keeps every other falsy value: 0 ?? 50 is 0, "" ?? "none" is "", false ?? true is false. Use ?? for defaults whenever 0, an empty string or false are valid answers (a volume of 0, an empty search, a setting turned off). Use || when any falsy value should be replaced. ?? short-circuits too.

  5. 5

    **Logical assignment (since ES2021)** combines these with =. a ||= b assigns b only if a is falsy; a &&= b assigns only if a is truthy; a ??= b assigns only if a is nullish. They are not quite a = a || b: when no assignment is needed, nothing is written at all, which matters for setters, read-only properties and const variables. options.retries ??= 3 is the idiomatic way to fill in a missing setting.

  6. 6

    Precedence: ! runs before everything else here, then &&, then ||. So a || b && c means a || (b && c). ?? has the same low level as ||, but the language forbids mixing ?? with && or || without parentheses: a || b ?? c is a SyntaxError. Write (a || b) ?? c to say what you mean. A related ES2020 operator, optional chaining ?., stops a property read at null/undefined and pairs naturally with ??: user?.name ?? "Guest" (Topic 6.2 covers it in depth).

Explain it without notes

01

What do && and || actually return?

02

What is short-circuit evaluation, and how is it useful?

03

When should you use ?? instead of ||?

04

What do ||=, &&= and ??= do?

Practice

01

Given const input = "";, print the input or the text (empty) if it's empty.

02

Given const page = { size: 0 };, set size to 20 only if it is missing, and add sort: "name" if sort is missing. Print the object.

03

Predict, then check: console.log(null ?? 0 || 5) — why doesn't it run?

Trade-offs

  • ↔

    a && doThing() is short, but an if statement is easier to read and to set a breakpoint on; keep short-circuit tricks for expressions, not as a replacement for if.

  • ↔

    ?? is precise but newer (ES2020): every current browser supports it, but very old targets need a transpiler.

Done when you can

  • I can predict what &&, || and ! return for any values.

  • I can use short-circuiting to guard property access and to give fallbacks.

  • I can choose between || and ?? for a default value.

  • I can use ||=, &&= and ??= and explain when they assign.

  • I know ?? can't be mixed with &&/|| without parentheses.