← Fundamentals
Guided lesson 5 questions · 6 languages

Conditions and truthiness

What an if tests, what counts as true, and which branch wins.

An if runs a block only when its condition is true. In TypeScript, any value can be a condition: 0 and "" count as false, and so do a few other values. When several tests are chained, the first true one wins.

Before you begin

Why this matters

TypeScript lets you write any value as a condition, and it decides by fixed rules whether that value counts as true. Knowing those rules lets you predict which block runs, and see why a real count of 0 is sometimes treated as missing.

What this helps you reason about

  • Predict which block of an if or an else-if chain runs.
  • List the values a condition treats as false, and spot a 0 or an empty text that is skipped by mistake.
  • Choose between || and ?? for a default value.

A jigsaw puzzle is either complete or it has pieces left. An if lets a program act on that. This lesson shows which block an if runs, and what TypeScript accepts as a condition: any value at all.

Already know this? The short version
  • An if runs its block when the condition is truthy and its else block otherwise; the falsy values are false, 0, -0, 0n, "", NaN, null, and undefined, and everything else, including "0" and [], is truthy.
  • if (count) skips a real 0; compare with what “missing” means instead (count !== undefined), and use ?? rather than || for a default, because count || 10 replaces 0.
  • In an else-if chain the first truthy test wins, so put the most specific test first.
  • && and || stop at the first side that decides the result, and they return one of their operands, not always a boolean; < and <= differ only at the boundary value.

Go to the final check· Go to the practice tickets

Words in this lesson (7)
boolean
A value that is either true or false. A comparison such as 12 < 20 gives a boolean.
if statement
A statement that runs a block of code when its condition is true. An else part, if there is one, runs when the condition is false.
condition
The test that decides whether an if runs its block, such as piecesLeft === 0. Any value can be written there; JavaScript treats it as true or false by the truthiness rules.
truthy and falsy
A value is falsy when a condition treats it as false: false, 0, -0, 0n, "", NaN, null, and undefined. Every other value is truthy, including "0" and an empty array [].
else-if chain
A sequence of if … else if … else. The tests are checked from top to bottom; the first one that is true runs its block, and the rest are skipped. The final else runs when no test is true.
boundary value
The value where a range starts or ends, such as 1000 in “below 1000”. < and <= give different answers for this value only.
nullish coalescing (??)
The operator a ?? b. It gives b only when a is undefined or null, so it keeps "" and 0. a || b gives b whenever a is falsy, including "" and 0.

Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.

01 / If

What does an if statement do?

An if statement runs a block of code only when its condition is true. The condition is the test in parentheses after if. The block is the code between the braces that follow.

The else part is optional. Its block runs when the condition is false. Either way, the program then continues with the line after the whole if statement.

The usual condition is a comparison. As What is a value? showed, a comparison such as piecesLeft === 0 gives a boolean: true or false. === means “is equal to”, and !== means “is not equal to”. Equality and comparison explains them in detail.

main.ts
let piecesLeft = 12;
if (piecesLeft === 0) {
  console.log("puzzle complete");
} else {
  console.log(piecesLeft + " pieces left"); // runs
}
console.log("box closed"); // runs either way

An if runs its block when the condition is true and its else block when it is false; then the program continues after it.

Experiment

Follow an if into one of its two blocks

The condition is computed first. When it is true, the block after if runs; when it is false, the else block runs. Exactly one of the two runs, and then the program continues after the whole if statement.

Why it matters To know which lines of an if run, compute its condition first, with the values the variables hold at that moment.

Predict which lines run and what is printed. Then change let piecesLeft = 12; to let piecesLeft = 0; and predict again.

let piecesLeft = 12;
if (piecesLeft === 0) {
  console.log("puzzle complete");
} else {
  console.log(piecesLeft + " pieces left");
}
console.log("box closed");
What to look for

At the if line, piecesLeft shows 12. The next stop is the console.log line, inside else: the stepper never stops on the "puzzle complete" line. The run prints 12 pieces left, then box closed. This debugger sometimes stops on the same line twice; that is normal. After the edit, the stepper stops on the "puzzle complete" line and skips the else block, and the run prints puzzle complete, then box closed.

Expected output

12 pieces left
box closed

02 / Conditions

What can be written as a condition?

In TypeScript, a condition does not have to be a boolean. You can write any value there, and JavaScript decides whether it counts as true or false. A value that counts as false is called falsy. There are eight: false, 0, -0, 0n, "" (empty text), NaN, null, and undefined. Every other value is truthy, including "0", "false", and an empty array [].

You do not need most of these words yet. -0 and NaN (“not a number”) are special numbers, and 0n is a zero of the bigint type. null and undefined both mean “no value”. An array is a list of values, and [] is an empty list. Later lessons cover each of them.

This lets you write if (name) to ask “is there any text?”. The trap is a number. if (edgePieces) does not ask “is there a number?”; it asks “is the number something other than 0?”. A real count of 0 is falsy, so the else block runs as if nothing had been counted.

When 0 is real data, write the test you mean: edgePieces !== undefined, or edgePieces > 0. Below, the type number | undefined means that edgePieces holds a number, or undefined before anything is counted.

main.ts
let edgePieces: number | undefined = 0;
if (edgePieces) {
  console.log("edge pieces found: " + edgePieces);
} else {
  console.log("not counted yet"); // runs: 0 is falsy
}
if (edgePieces !== undefined) {
  console.log("edge pieces found: " + edgePieces); // runs
}

In a condition, false, 0, -0, 0n, "", NaN, null, and undefined count as false. When 0 or "" is real data, compare instead of testing the value by itself.

Coming from Python: The rules are close: 0, "", and None are false in both. But an empty list [] is false in Python and truthy in JavaScript.

Coming from Java: Java refuses a number as a condition; TypeScript accepts it and treats 0 as false.

Precisely: ToBoolean and TypeScript’s checks

The rule is the ToBoolean operation in the ECMAScript specification. TypeScript does not require a boolean in a condition. It reports an error for a few conditions that can never be false: a function that is named but not called gives This condition will always return true since this function is always defined. Did you mean to call it instead?, and a literal such as "0" gives This kind of expression is always truthy. Lint tools, such as the strict-boolean-expressions rule of typescript-eslint, can require a boolean everywhere.

Experiment

Test a count of 0 by itself

JavaScript turns the condition’s value into true or false. 0 is falsy, so if (edgePieces) treats a real count of 0 as false. edgePieces !== undefined is a comparison, and it is true for every number, 0 included.

Why it matters When 0 or "" is real data, do not test the value by itself. Compare it with what “missing” means, such as undefined.

Predict which message is printed: does a count of 0 count? Then change if (edgePieces) to if (edgePieces !== undefined) and predict again.

let edgePieces: number | undefined = 0;
if (edgePieces) {
  console.log("edge pieces found: " + edgePieces);
} else {
  console.log("not counted yet");
}
What to look for

At the if line, edgePieces shows 0. The next stop is the "not counted yet" line, and the run prints not counted yet, although 0 edge pieces were counted. After the edit, the stepper stops on the "edge pieces found" line, and the run prints edge pieces found: 0.

Expected output

not counted yet

03 / Chains

Which branch runs when several tests are true?

An if statement can test several conditions in turn: if … else if … else. This is an else-if chain. The tests are checked from top to bottom. The first test that is true runs its block, and the rest of the chain is skipped, even tests that would also be true. The final else runs when no test was true.

So the order matters when two tests can both be true. A puzzle of 1500 pieces has at least 500 pieces and also at least 1000. If the test for 500 comes first, it catches every puzzle of 500 pieces or more, and the "huge" branch never runs. Put the most specific test first: the test that is true for the fewest values.

main.ts
let pieces = 1500;
let size = "";
if (pieces >= 1000) {
  size = "huge";
} else if (pieces >= 500) {
  size = "large"; // 500 to 999
} else {
  size = "small";
}

In an else-if chain the first true test wins; put the most specific test first.

Experiment

Put a general test before a specific one

The tests are checked from the top, and the first true one decides. 1500 >= 500 is true, so the chain stops there. The test for 1000 is true as well, but it is never reached.

Why it matters When two tests in a chain can both be true, the earlier one wins. Put the most specific test first.

Predict what is printed. Then swap the first two branches, so that the test pieces >= 1000 with "huge" comes first, and predict again.

let pieces = 1500;
let size = "";
if (pieces >= 500) {
  size = "large";
} else if (pieces >= 1000) {
  size = "huge";
} else {
  size = "small";
}
console.log(pieces + " pieces: " + size);
What to look for

At the first if, pieces shows 1500 and size shows "". The next stop is size = "large"; the stepper never stops on the else if line or on the "huge" line. size then shows "large", and the run prints 1500 pieces: large. After the edit, the stepper stops on the "huge" line, and the run prints 1500 pieces: huge.

Expected output

1500 pieces: large

04 / Combining

How do you combine two tests?

&& (“and”) is true when both tests are true. || (“or”) is true when at least one is true. ! (“not”) turns true into false and false into true. What is a value? introduced them; in a condition, they let one if test several things.

pieces >= 500 && pieces < 1000 is true from 500 to 999. At the end of a range, choose < or <= on purpose. They differ for one value, the boundary value itself: pieces < 1000 is false for 1000, and pieces <= 1000 is true. Read the rule you are writing: “up to 1000” and “below 1000” are different conditions.

&& and || also stop early. When the left side already decides the answer, the right side is not computed. This is called short-circuit evaluation, and a later topic covers it.

In TypeScript, || does not always give a boolean. a || b gives a when a is truthy, and b otherwise. So count || 10 gives 10 when count is 0. To replace only a missing value, use ??, called nullish coalescing: count ?? 10 gives 10 only when count is undefined or null, and keeps 0.

main.ts
let pieces = 1000;
console.log(pieces >= 500 && pieces < 1000); // false
console.log(pieces >= 500 && pieces <= 1000); // true
let count: number | undefined = 0;
console.log(count || 10); // 10: 0 is falsy
console.log(count ?? 10); // 0: 0 is not missing

Combine tests with &&, ||, and !, and decide at each boundary whether the boundary value is inside: < leaves it out, <= keeps it. For a default value, use ?? when 0 and "" are real data.

Coming from React: {count && <Badge />} renders the number 0 when count is 0, because && gives its left side when that side is falsy. Write {count > 0 && <Badge />}.

05 / Try it

Can you explain a fresh example?

Decide which test in the chain is true first, then work out the last line. The next lesson, Function calls, shows how a program runs code that is written somewhere else.

main.ts
let pieces = 300;
let left = 0;
if (left < 10) {
  console.log("nearly done");
} else if (left === 0) {
  console.log("finished");
}
console.log(left || "none", left ?? "none", pieces <= 300 && !left);

What is printed?

Reveal the reasoning

nearly done, then none 0 true. left is 0, so the first test, left < 10, is already true. The chain stops there, and left === 0 is never checked, although it is true too. left || "none" gives "none", because 0 is falsy. left ?? "none" keeps 0, which is not undefined or null. pieces <= 300 is true at the boundary value 300, and !left is true, because 0 is falsy.

Practice tickets

  1. COND-101 · A self-timer set to 0 still waits 10 seconds → Free
  2. COND-102 · Children with a membership still pay at the door → Sign in · Pro
  3. COND-103 · A 300-times eyepiece pair is listed as high power → Sign in · Pro

Keep exploring

Function calls →

Primary references