RizTech Academy logo
RizTech Academy
Language BasicsLesson 4 of 525 min

Operators, conditions and truthiness

A program that cannot decide anything is a calculator. This lesson is where your code starts branching — and where it starts getting one particular thing wrong, in a way that survives testing and reaches real users.

Arithmetic

console.log(7 + 3);
console.log(7 - 3);
console.log(7 * 3);
console.log(7 / 3);
console.log(7 % 3);
console.log(2 ** 10);
10
4
21
2.3333333333333335
1
1024

Two worth a note. / always gives a decimal — there is no separate integer division, so 7 / 2 is 3.5, not 3. And % is the remainder, which is how you test divisibility: n % 2 === 0 means even.

% keeps the sign of the left-hand side, which catches people:

console.log(-7 % 3);
-1

Not 2. If you are using % to wrap an index around, a negative input will produce a negative result rather than wrapping.

Shorthands you will read constantly:

Shorthand Means
x += 5 x = x + 5
x -= 5 x = x - 5
x *= 2 x = x * 2
x++ Add one
x-- Subtract one

Comparison

Operator Meaning
=== Equal, no conversion. Use this one.
!== Not equal, no conversion.
== / != Equal after conversion. Avoid, except == null.
> < >= <= Ordering.

The last lesson covered why === wins. Comparison operators still coerce, so '10' > 9 is true — only === refuses to convert.

Logical operators

&& is and, || is or, ! is not.

const plates = 3;
const isPaid = true;

console.log(plates > 0 && isPaid);
console.log(plates > 5 || isPaid);
console.log(!isPaid);
true
true
false

They short-circuit, which matters more than it sounds. && stops at the first falsy value; || stops at the first truthy one. So the right-hand side may never run:

const order = null;
console.log(order && order.plates);
null

Because order is falsy, order.plates is never evaluated — which is the only reason that line does not crash. This idiom is common in older code. Module 4 replaces it with ?., which says what it means.

The other half of short-circuiting is that && and || return one of their operands, not true or false. null && anything is null, not false. That is the foundation of the bug below.

Truthiness

Anywhere a condition is expected, a non-boolean gets converted. Seven values are falsy. Everything else is truthy.

Falsy
false
0 and -0
'' the empty string
null
undefined
NaN
0n bigint zero

Everything else is truthy — including several things people assume are not:

console.log(Boolean('0'));
console.log(Boolean('false'));
console.log(Boolean([]));
console.log(Boolean({}));
console.log(Boolean(' '));
true
true
true
true
true

'0' and 'false' are truthy because they are non-empty strings. An empty array is truthy because it is an object, and objects are always truthy. That last one bites: if (items) is true even when items is empty. Use items.length.

The bug: a real zero looks like nothing

Here is the whole reason this lesson exists.

function describeOrder(plates) {
  if (plates) {
    console.log(`${plates} plates today.`);
  } else {
    console.log('No plate count recorded.');
  }
}

describeOrder(3);
describeOrder(0);
3 plates today.
No plate count recorded.

The second call is wrong. Zero plates was recorded — the customer cancelled, and zero is the answer. But 0 is falsy, so if (plates) treats a genuine measurement as a missing one.

This is not a contrived example. Any count, price, quantity, temperature or score that can legitimately be zero has this bug waiting in it, and it never shows up in testing because nobody tests with zero.

Ask the question you actually mean:

function describeOrder(plates) {
  if (plates === undefined || plates === null) {
    console.log('No plate count recorded.');
  } else {
    console.log(`${plates} plates today.`);
  }
}

describeOrder(3);
describeOrder(0);
3 plates today.
0 plates today.

The same bug wearing different clothes, with || supplying a default:

const quantity = 0;
console.log(quantity || 1);
1

A customer ordering zero gets one. Because || returns the right-hand side whenever the left is falsy, and zero is falsy.

The fix is ??, the nullish coalescing operator, which only steps in for null and undefined:

console.log(0 ?? 1);
console.log('' ?? 'none');
console.log(null ?? 1);
0

1

The middle line printed an empty string — '' is falsy but not nullish, so ?? left it alone. Module 4 covers ?? properly; the rule to carry from here is || for "or", ?? for defaults.

if, else if, else

const plates = 12;

if (plates === 0) {
  console.log('Cancelled.');
} else if (plates < 10) {
  console.log('Standard order.');
} else {
  console.log('Bulk order — ask about the discount.');
}
Bulk order — ask about the discount.

The first matching branch runs and the rest are skipped. Always use braces, even for one line — a brace-less if that later gains a second line is a classic bug.

Ternary

For choosing between two values, not for doing two things:

const plates = 3;
const label = plates === 1 ? 'plate' : 'plates';
console.log(`${plates} ${label}`);
3 plates

Excellent inside a template literal. Do not nest them — a nested ternary is harder to read than the if it replaced, and you will be the one reading it.

switch

When one value is compared against many fixed options:

const status = 'delivered';

switch (status) {
  case 'pending':
    console.log('Not yet cooked.');
    break;
  case 'delivered':
    console.log('Delivered. Collect the tiffin.');
    break;
  default:
    console.log('Unknown status.');
}
Delivered. Collect the tiffin.

break is not optional. Without it, execution falls through into the next case and keeps going — a genuine and much-cursed source of bugs. switch also compares with ===, so case '1': will not match the number 1.

For anything more complicated than matching a fixed value, use if/else if.

Check your work

7 / 3 is 2.3333333333333335 and 7 % 3 is 1. There is no integer division; / always produces a decimal.

-7 % 3 is -1, not 2. The remainder keeps the sign of the left operand.

Boolean('0'), Boolean('false'), Boolean([]) and Boolean({}) are all true. Non-empty strings are truthy whatever they say, and objects and arrays are always truthy. For an array, test items.length.

The seven falsy values are false, 0, -0, '', null, undefined, NaN — plus 0n. Everything else is truthy.

describeOrder(0) with if (plates) says "No plate count recorded", and that is the bug. Zero is falsy, so a real measurement of zero is indistinguishable from a missing one. Test plates === undefined || plates === null instead — or plates == null, which is the one sanctioned use of ==.

quantity || 1 where quantity is 0 gives 1. || fires on any falsy value. 0 ?? 1 gives 0, because ?? fires only on null and undefined. '' ?? 'none' gives '' for the same reason.

order && order.plates where order is null gives null, not false. && returns one of its operands. It does not crash because && short-circuits and never evaluates order.plates.

A switch case without break falls through into the following case and runs that too. Dropping the break from case 'pending' makes a pending order print both "Not yet cooked." and "Delivered. Collect the tiffin."

deliveryCharge:

function deliveryCharge(distanceKm, orderTotal) {
  if (distanceKm > 15) return null;
  if (orderTotal >= 500) return 0;
  return distanceKm <= 5 ? 30 : 50;
}

The refusal goes first, because a 20 km order should be refused whatever it is worth — putting the free-delivery check above it would quietly accept an undeliverable order. At the boundaries: ₹500 exactly is free (>=), 5 km exactly is ₹30 (<=), and 15 km exactly is still allowed at ₹50. If you wrote > or < anywhere in there, one of those three flips, which is why the boundaries are the exercise.

Practice

  1. Print the results of +, -, *, /, % and ** on 7 and 3. Predict each first, particularly /.
  2. Write a check for whether a number is even using %. Then try it with -4 and -7 and confirm it still behaves.
  3. Run Boolean() over all seven falsy values, then over '0', 'false', [], {} and ' '. Two of the truthy answers should annoy you.
  4. Write the zero bug on purpose. Build describeOrder with if (plates) and call it with 3, 0 and nothing at all. Then fix it so that only the missing case reports as missing. This is the most valuable thing in the lesson.
  5. Do the same with defaults: const qty = input || 1 where input is 0. Watch a zero order become one. Fix it with ??.
  6. Write an if/else if/else that labels an order as cancelled, standard or bulk, and test it at the boundaries — 0, 9, 10.
  7. Use a ternary inside a template literal to print 1 plate or 3 plates with correct pluralisation.
  8. Write a switch on a delivery status. Then delete one break and work out from the output exactly what fell through, before putting it back.
  9. Prove short-circuiting: write false && somethingUndefined() and confirm it does not crash. Then swap to true && and watch it do so.
  10. Harder. Write deliveryCharge(distanceKm, orderTotal) — free over ₹500, ₹30 up to 5 km, ₹50 beyond that, and refuse anything over 15 km. Decide which checks come first, and test ₹500 exactly, 5 km exactly and 15 km exactly. The boundaries are the whole exercise.

Next: loops — repeating work, and the loop that hands you string indices when you wanted numbers.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship