Programs that run top to bottom exactly once are rare. Usually you need to make a decision — is the user logged in? is the cart empty? — or repeat an action — process every item, retry until it works. Conditionals and loops are the two control structures that make code useful in the real world.
A conditional runs one block of code or another depending on whether a test is true. The simplest form is if, but real code almost always needs else if chains and else fallbacks. A loop repeats a block while a condition stays true, or while there are more items to visit.
Mastering these two structures means moving from scripts that calculate one thing to programs that handle any input, any size, and any edge case.
flowchart TD
S["score = 85"] --> Q{"score >= 90?"}
Q -- "Yes" --> A["Grade: A"]
Q -- "No" --> Q2{"score >= 80?"}
Q2 -- "Yes" --> B["Grade: B"]
Q2 -- "No" --> Q3{"score >= 70?"}
Q3 -- "Yes" --> C["Grade: C"]
Q3 -- "No" --> F["Grade: F"]
style A fill:#e0900b,color:#fff
style B fill:#e0900b,color:#fff
Comparisons: === versus ==
JavaScript has two equality operators, and they do not mean the same thing. === checks both value and type; == tries to convert types first, which leads to famously surprising results:
0 == "" // true — string becomes 0
0 === "" // false — number !== string
null == undefined // true
null === undefined // false
The rule is simple: always use === and !== unless you have a specific reason not to. Code that relies on == is harder to read and easier to break when data changes. The same discipline applies to comparisons: >, <, >=, and <= coerce as well, so make sure both sides are the expected type.
One subtle case: NaN === NaN is false. If you need to test for NaN, use Number.isNaN(value).
console.log(0 == ""); console.log(0 === ""); console.log(5 > "3"); console.log(5 > "10");
Logical operators and truthiness
Combine checks with && (both must be true), || (either is true), and ! (not). These operators short-circuit: && stops and returns the first falsy value it meets; || stops and returns the first truthy value. This is not just an optimisation — it is an idiom:
const name = input || "Anonymous"; // default if input is falsy
Conditions also read truthiness. 0, "", null, undefined, NaN, and false are falsy; everything else is truthy. That means if (count) is shorthand for if (count !== 0 && count !== null && count !== undefined), but it is also a common source of bugs when 0 is a valid value you did not mean to exclude.
A defensive habit: when 0 or "" are legitimate values, be explicit. Write if (count !== 0) rather than if (count). The extra characters prevent a whole class of silent errors.
flowchart TD
T["Truthy examples"] --> T1["'hello'"]
T --> T2["42"]
T --> T3["[]"]
T --> T4["{}"]
F["Falsy values"] --> F1["0"]
F --> F2["''"]
F --> F3["null"]
F --> F4["undefined"]
F --> F5["NaN"]
F --> F6["false"]
style T fill:#3776ab,color:#fff
style F fill:#b45309,color:#fff
Loops: repeating work
Loops run a block of code multiple times. The classic for loop gives you precise control over a counter:
for (let i = 0; i < 3; i++) {
console.log(i);
}
for...of is the friendliest way to walk through an array — you get each element directly, no index juggling. Use while when you do not know how many iterations you need in advance, only the condition that should stop them:
let attempts = 3;
while (attempts > 0) {
attempts--;
}
All three share the same risk: an infinite loop if the exit condition never becomes false. With for the increment is visible in the header, which makes the off-by-one easier to spot. With while the update is somewhere in the body, so it is easier to forget.
const cart = [12, 7, 30];
let total = 0;
for (const price of cart) {
total += price;
}
console.log(`Total: ${total}`);
for (let i = 1; i <= 3; i++) {
console.log(`Item ${i}`);
}
let n = 3;
while (n > 0) {
console.log(n);
n--;
}
flowchart LR
INIT["let i = 0"] --> TEST{"i < 3?"}
TEST -->|"Yes"| BODY["console.log(i)"]
BODY --> INC["i++"]
INC --> TEST
TEST -->|"No"| END["done"]
style BODY fill:#e0900b,color:#fff
style END fill:#e0900b,color:#fff
let sum = 0;
let i = 1;
while (sum < 10) {
sum += i;
console.log(`added ${i}, sum is ${sum}`);
i++;
}
console.log(`finished at i=${i}`);
Write gradeLabel(score) that returns "A" for 90+, "B" for 80+, "C" for 70+, and "F" otherwise.
function gradeLabel(score) {
// return "A", "B", "C", or "F"
}
Check the highest threshold first, then work down.
Each
ifcanreturnimmediately — once you return, the rest is skipped.
function gradeLabel(score) {
if (score >= 90) return "A";
if (score >= 80) return "B";
if (score >= 70) return "C";
return "F";
}
What does this print? It loops the array and counts how many values are greater than 4.
const nums = [5, 2, 8, 1, 9];
let count = 0;
for (const n of nums) {
if (n > 4) {
count++;
}
}
console.log(count);
5, 8, and 9 are each greater than 4.
So count is incremented three times.
Write fizzBuzz(n) that returns an array of strings from 1 to n. For multiples of 3 use "Fizz", multiples of 5 use "Buzz", multiples of both use "FizzBuzz", otherwise use the number as a string. So fizzBuzz(5) returns ["1","2","Fizz","4","Buzz"].
function fizzBuzz(n) {
// return the array
}
Check the combined condition (both 3 and 5) first, or the simpler ones will match first.
Use String(i) to turn a number into its string form.
function fizzBuzz(n) {
const out = [];
for (let i = 1; i <= n; i++) {
if (i % 3 === 0 && i % 5 === 0) {
out.push("FizzBuzz");
} else if (i % 3 === 0) {
out.push("Fizz");
} else if (i % 5 === 0) {
out.push("Buzz");
} else {
out.push(String(i));
}
}
return out;
}
This loop is supposed to print 1, 2, 3 and then stop, but it runs forever. Find and fix the bug.
let i = 1;
while (i <= 3) {
console.log(i);
}
The loop condition never becomes false because nothing changes inside the body.
Increment i at the end of each iteration so the loop can exit.
let i = 1;
while (i <= 3) {
console.log(i);
i++;
}
Write sumUntilTarget(nums, target) that adds numbers from nums in order until the running total is at least target, then returns that total. If nums runs out before reaching target, return the total of all numbers. Use a for...of loop and break.
function sumUntilTarget(nums, target) {
// return the total once it reaches target
}
Use a for...of loop to walk through nums.
Add each number to total, then check if total >= target.
Use break to stop the loop early once the target is reached.
function sumUntilTarget(nums, target) {
let total = 0;
for (const n of nums) {
total += n;
if (total >= target) {
break;
}
}
return total;
}
Recap
if / else if / elseruns the first branch whose condition is true.===and!==are strict; avoid==unless you truly need coercion.NaN === NaNisfalse; useNumber.isNaN()to test for it.&&,||, and!combine conditions and short-circuit.for...ofis the cleanest way to iterate array elements.whilerepeats while a condition holds — make sure something inside changes it.- Off-by-one bugs come from
<versus<=; array indexes stop atlength - 1. - Be explicit when
0or""are valid values; truthiness is a bug magnet.
Next you'll meet arrays: ordered lists of values and the methods that make them powerful.