Lesson 0.6 · JavaScript in general
Conditions and logical operators
Truthy and falsy, the ternary, &&, ||, ??, ?., switch and early return.
Loading lesson…
Lesson 0.6 · JavaScript in general
Truthy and falsy, the ternary, &&, ||, ??, ?., switch and early return.
Loading lesson…
A program runs like a train on rails – line after line. Conditions are the switches: depending on a question, they send the train one way or the other. "Is the user signed in?" "Is the cart empty?" "Has the data arrived from the server?"
JavaScript has two groups of tools for switches. Statements (if, switch) control which lines run. Operators (? :, &&, ||, ??, ?.) pick a value right inside an expression – and those are the ones you'll meet most in React, because you can't write an if inside JSX.
const age = 17if (age >= 18) {console.log('Adult')} else if (age >= 13) {console.log('Teenager')} else {console.log('Child')}// The one-line form – without braces only for a single statementif (age < 0) return
When a function has to check several things, don't nest the conditions inside each other. Deal with the exceptions first and stop, and leave the main case for the end:
// ❌ A pyramidfunction checkout(cart, user) {if (user) {if (cart.length > 0) {if (user.address) {return sendOrder(cart, user)} else {return 'The address is missing'}} else {return 'The cart is empty'}} else {return 'Please sign in'}}// ✅ Early return – reads top to bottom like a checklistfunction checkout(cart, user) {if (!user) return 'Please sign in'if (cart.length === 0) return 'The cart is empty'if (!user.address) return 'The address is missing'return sendOrder(cart, user)}
When JavaScript needs a yes/no (in an if, a ternary, with &&) and gets something other than a boolean, it converts it. There are only six false (falsy) values – everything else is true (truthy):
| Falsy – counts as NO | Truthy – counts as YES (even when it doesn't look like it) |
|---|---|
false | true |
0 | 42, -1, 0.5 |
'' (empty text) | '0', 'false', ' ' (a space) |
null | [] (an empty array) |
undefined | {} (an empty object) |
NaN | every function, every date… |
if ('hello') { … } // runsif ('') { … } // doesn't runif (0) { … } // doesn't runif ([]) { … } // ⚠️ runs – an empty array is truthy!if (items.length > 0) // ✅ the right question "does the array contain anything?"if (items.length) // works too (0 is falsy), but it's less clear what you're askingif (Object.keys(obj).length > 0) // "does the object have any properties?"Boolean('hello') // true – conversion to a boolean!!'hello' // true – the same as a shortcut (double negation)
const age = 25const hasTicket = trueage >= 18 && hasTicket // true – AND: both must holdage < 18 || hasTicket // true – OR: one is enough!hasTicket // false – NOT: flips the value// Combinations – parentheses for safety and readabilityconst canEnter = (age >= 18 && hasTicket) || isVipconst isWorkday = day !== 'saturday' && day !== 'sunday'const isWeekend = ['saturday', 'sunday'].includes(day) // often easier to read
A ternary is an if/else squeezed into an expression: condition ? valueIfYes : valueIfNo. Unlike if, it has a value, so you can store it in a variable or put it anywhere in an expression.
const label = isDone ? 'Done' : 'To do'const price = isMember ? basePrice * 0.9 : basePriceconst greeting = `Good ${hour < 12 ? 'morning' : 'afternoon'}`const className = `btn ${isActive ? 'btn-active' : ''}`// The same with if – longer and needs letlet labelif (isDone) {label = 'Done'} else {label = 'To do'}
Here's the catch: in JavaScript, && and || don't return true/false. They return one of their sides:
| Expression | When a is truthy | When a is falsy |
|---|---|---|
a && b | returns b | returns a |
a || b | returns a | returns b |
'hello' && 'world' // 'world''' && 'world' // ''0 && 'world' // 0 ← careful, the number 0!null && 'world' // null'hello' || 'default' // 'hello''' || 'default' // 'default'0 || 'default' // 'default' ← the zero "got lost"// Typical useuser && user.name // read name only if user exists (today rather user?.name)const name = input || 'Anonymous' // a default value for an empty inputisLoggedIn && showWelcome() // call it only when the condition holds
a ?? b returns b only when a is null or undefined. Zero, empty text and false are valid values for it.
const settings = { volume: 0, nickname: '', notifications: false }settings.volume || 50 // 50 ❌ the user muted it to 0 and we turned it up to 50settings.volume ?? 50 // 0 ✅settings.notifications || true // true ❌ the user turned them offsettings.notifications ?? true // false ✅settings.theme ?? 'light' // 'light' – the property is missing → the default value// Assignment with a default valueoptions.retries ??= 3 // set it to 3 only if there's nothing there
| Value a | a || "x" | a ?? "x" |
|---|---|---|
0 | "x" | 0 |
'' | "x" | '' |
false | "x" | false |
null | "x" | "x" |
undefined | "x" | "x" |
'hello' | 'hello' | 'hello' |
const user = { name: 'Anna', address: null }user.address.city // 💥 TypeError: Cannot read properties of nulluser.address?.city // undefined – ?. stops and returns undefineduser.contacts?.[0] // undefined – works with square brackets toouser.getAge?.() // undefined – and with calling a function that may not exist// The typical pair: read safely + a default valueconst city = user.address?.city ?? 'unknown'// Data from the server that hasn't arrived yetconst firstTitle = response?.data?.items?.[0]?.title ?? 'No items'
What you see: A row of values for x and, below it, what converting to a boolean, a ternary, &&, || and ?? make of each one.
Try it:
x && 'right' returns 0 (not false!), || replaces the zero, ?? keeps it.|| and ?? differ, for null they don't.The takeaway: There are only six falsy values. && and || return one of the sides, not a boolean. ?? only reacts to null and undefined.
Loading the interactive part…
When you compare one value with many options, switch is clearer than a chain of ifs:
function statusLabel(status) {switch (status) {case 'new':return 'New order'case 'paid':return 'Paid'case 'shipped':case 'delivered': // several cases with the same resultreturn 'On its way'default:return 'Unknown status'}}
In functions that just turn one value into another, a dictionary (an object) is often even shorter:
const STATUS_LABELS = { new: 'New order', paid: 'Paid', shipped: 'On its way' }const statusLabel = (status) => STATUS_LABELS[status] ?? 'Unknown status'const ICONS = { success: '✅', error: '❌', warning: '⚠️' }const icon = ICONS[type] ?? 'ℹ️'
You'll see switch, on the other hand, in React reducers (lesson 5), where every action does something different, not just returns a value.
One ternary and one ??.
CHALLENGE: Who is who
1. ageLabel(20) should return 'adult', ageLabel(12) 'child'. The limit is 18. Use a ternary:
condition ? 'adult' : 'child'
2. nameOrGuest('Anna') should return 'Anna', nameOrGuest(null) 'Guest'. Use ??Loading the interactive part…
Six functions that make decisions and safely read data that may be missing. Watch out for the difference between || and ?? – the tests will check it.
Medium and hard challenges are a bonus for signed-in readers. Signing up is free – just an e-mail, no password.
false, 0, '', null, undefined and NaN. An empty array and an empty object are truthy.a ? b : c is an if/else that returns a value. Don't nest more than one.&& and || return one of the sides, not a boolean – 0 && x is 0.?? fills in a default value only for null/undefined, || also for 0 and ''. You almost always want ??.?. safely reads from a value that may be missing. Typically a?.b ?? default.Checks, Check)Checks, Check – from the file src/course/checks.tsx. The course helper for function-style challenges: runs the tests and shows ✅/❌. You don't need to know how it works inside – it just tells you which steps are done./** One test: a description and a function returning true (passed) or false. It may be async. */export type Check = [name: string, test: () => boolean | Promise<boolean>]/*** Runs the tests of a challenge and shows ✅ or ❌ for each.* An error thrown in a test (e.g. when trying to change a frozen object) is shown under the test.*/export function Checks({ tests }: { tests: Check[] }) {const t = useT()const [results, setResults] = useState<Result[]>(() => tests.map(() => ({ status: 'pending' })))useEffect(() => {let ignore = falsetests.forEach(([, test], i) => {Promise.resolve().then(test).then((ok): Result => (ok ? { status: 'ok' } : { status: 'fail' }),(e): Result => ({ status: 'fail', error: e instanceof Error ? e.message : String(e) }),).then((result) => {if (ignore) returnsetResults((prev) => prev.map((r, idx) => (idx === i ? result : r)))})})return () => {ignore = true}}, [tests])const passed = results.filter((r) => r.status === 'ok').lengthreturn (<div className="stack"><strong className={passed === tests.length ? 'ok' : undefined}>{t.checksPassed(passed, tests.length)}</strong><ul className="list">{tests.map(([name], i) => {const r = results[i] ?? { status: 'pending' }return (<li key={name}>{r.status === 'pending' ? '⏳' : r.status === 'ok' ? '✅' : '❌'} {name}{r.status === 'fail' && r.error && (<div className="bad muted">{t.checksError} {r.error}</div>)}</li>)})}</ul></div>)}