Values, Branches, and Loops
Introduction
This lesson opens the second chapter of the course and matches the JavaScript fundamentals of course 2 of the Meta Front-End Developer pathway ("Programming with JavaScript"). In course 1 you built static pages. From now on pages react to people: they validate a form, compute a price, and show different content for different data. All of that rests on four ideas: values, variables, decisions (branches), and repetition (loops). The pathway does not publish exam weights, so this course treats JavaScript as a heavily practiced domain and gives it more questions than most.
By the end you will be able to declare variables with the right keyword, predict what an expression evaluates to (including surprising coercions), write conditions and loops that behave correctly at their edges, and find a bug with a breakpoint instead of guessing. The practice scenario is original: repair the Harbor Workshops price calculator that glues numeric strings together instead of adding them.
Learning objectives
- Choose between
constandlet, and explain whyvaris avoided in new code. - Name the primitive types and predict the result of
typeof, comparison, and arithmetic on mixed types. - Explain implicit type coercion and convert values explicitly and safely.
- Use
===, truthy/falsy rules,&&,||, and??correctly. - Write
if/else, ternary, andswitchbranches, andfor,for...of,whileloops. - Describe block scope and the temporal dead zone.
- Debug with
console.log, breakpoints, and the call stack in browser developer tools.
1. Values and variables
A value is a piece of data. A variable is a named binding to a value. JavaScript has seven primitive types you need now:
| Type | Example | Notes |
|---|---|---|
string |
"Harbor" |
Text; immutable |
number |
12.5, -3, NaN, Infinity |
One floating-point type for integers and decimals |
bigint |
9007199254740993n |
Integers beyond the safe range; rarely needed in UI work |
boolean |
true, false |
|
undefined |
undefined |
A variable that was declared but never assigned |
null |
null |
An intentional "no value" |
symbol |
Symbol("id") |
Unique keys; you will rarely write one |
Everything else (arrays, plain objects, functions, dates) is an object.
Declare variables with const or let:
const siteName = "Harbor Workshops"; // never reassigned
let seatsLeft = 12; // will change
seatsLeft = seatsLeft - 1; // fine
siteName = "Other"; // TypeError: Assignment to constant variable.
const means the binding cannot be reassigned, not that the value is frozen. A const object can still have its properties changed:
const workshop = { title: "Pottery", seats: 8 };
workshop.seats = 7; // allowed: same object, new property value
// workshop = {}; // TypeError: the binding is constant
Rule of thumb: start with const; switch to let only when you truly reassign. This makes code easier to read, because a reader knows a const name always points at the same value.
var is the older keyword. It is function-scoped rather than block-scoped, is hoisted with the value undefined, and allows silent redeclaration. These traits cause bugs, so modern code and the React tooling in this course avoid it. You must still recognize it when you read older code.
Naming. Use camelCase for variables and functions (seatsLeft), PascalCase for classes and, later, React components (WorkshopCard), and UPPER_SNAKE_CASE for true constants (MAX_SEATS). Names are case-sensitive and cannot start with a digit.
Worked example: predicting typeof
Task. Predict the output of each line before running it.
console.log(typeof "5"); // ?
console.log(typeof 5); // ?
console.log(typeof null); // ?
console.log(typeof undefined); // ?
console.log(typeof []); // ?
console.log(typeof (() => 1)); // ?
Reasoning. A string literal is a string, a number literal is a number. typeof null returns "object"; this is a historical quirk that cannot be fixed without breaking the web, so to test for null you write value === null. typeof undefined is "undefined". An array is an object, so typeof [] is "object"; use Array.isArray(value) to detect arrays. Functions report "function".
Output: string, number, object, undefined, object, function.
2. Numbers, strings, and coercion
Numbers are floating point
All JavaScript numbers are 64-bit floating-point values. Some decimals cannot be represented exactly:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log((0.1 + 0.2).toFixed(2)); // "0.30" (a string!)
For money, the safest habit is to compute in the smallest unit (cents) as integers and divide for display. Note that toFixed returns a string, so it belongs at the very end, when you format output.
Useful values and operators: % (remainder), ** (power), Infinity (from 10 / 0), and NaN, "not a number", which is the result of a failed numeric operation such as Number("abc"). NaN is never equal to anything, even itself, so use Number.isNaN(value).
Strings
Strings use single quotes, double quotes, or backticks. Template literals (backticks) allow embedded expressions and multi-line text:
const total = 25;
console.log(`Total: ${total.toFixed(2)} EUR`); // Total: 25.00 EUR
Strings have a length property and methods such as toUpperCase(), trim(), includes(), slice(), and split(). They are immutable: methods return new strings.
Implicit coercion
JavaScript converts types automatically when an operator mixes them. This is called coercion and it is the number-one source of beginner bugs. The key rules:
+with at least one string operand concatenates.-,*,/convert both sides to numbers.
console.log("5" + 3); // "53" (concatenation)
console.log("5" - 3); // 2 (numeric)
console.log("5" * "2"); // 10
console.log(+""); // 0
console.log(+"abc"); // NaN
Prefer explicit conversion so intent is visible:
Number("42"); // 42
Number("42px"); // NaN (strict: the whole string must be numeric)
parseInt("42px", 10); // 42 (reads a prefix)
parseFloat("3.5kg"); // 3.5
String(42); // "42"
Boolean("x"); // true
Always pass the radix 10 to parseInt. Use Number() when the whole string must be a valid number (form fields), and parseInt/parseFloat only when a prefix is acceptable. Remember that Number("") is 0, so an empty input silently becomes zero unless you check for it.
| Expression | Result | Why |
|---|---|---|
"5" + 3 |
"53" |
+ prefers string concatenation |
"5" - 3 |
2 |
- is numeric only |
Number(null) |
0 |
null converts to zero |
Number(undefined) |
NaN |
undefined has no numeric meaning |
Number(true) |
1 |
booleans convert to 1/0 |
3. Comparison, truthiness, and logical operators
Equality
== compares after coercion; === compares without coercion and also checks the type. Use === and !== by default.
console.log(1 == "1"); // true
console.log(1 === "1"); // false
console.log(0 == ""); // true (surprising)
console.log(null == undefined); // true
console.log(null === undefined);// false
console.log(NaN === NaN); // false
A single exception many teams accept is value == null, which is true for both null and undefined. In this course we use === everywhere and write the two checks explicitly when needed.
Objects are compared by reference: two separate {} are never equal, even if their contents match.
Truthy and falsy
In a condition, every value is treated as either true or false. There are exactly these falsy values: false, 0, -0, 0n, "", null, undefined, and NaN. Everything else is truthy, including "0", "false", [], and {}.
Boolean(""); // false
Boolean("0"); // true
Boolean([]); // true
Boolean({}); // true
Boolean(0); // false
&&, ||, ??
Logical operators return one of their operands, not necessarily a boolean:
console.log("a" || "b"); // "a" first truthy
console.log("" || "b"); // "b"
console.log(1 && "x"); // "x" last value if all truthy
console.log(0 && "x"); // 0 first falsy
console.log(0 || 10); // 10
console.log(0 ?? 10); // 0 ?? only replaces null/undefined
console.log(null ?? "b"); // "b"
The nullish coalescing operator ?? is the right tool for defaults when 0 or "" are valid values. For instance, a workshop with 0 seats left must show 0, not a default of 10:
const shown = seatsLeft ?? 10; // keeps 0
const wrong = seatsLeft || 10; // turns 0 into 10: a bug
Optional chaining ?. safely reads nested properties: workshop?.host?.name gives undefined instead of throwing if host is missing.
4. Branches
if / else
function seatMessage(seats) {
if (seats === 0) {
return "Sold out";
} else if (seats < 5) {
return `Only ${seats} left`;
}
return "Seats available";
}
Prefer early returns over deep nesting. Always use braces, even for one-line bodies; omitting them is a classic source of bugs when someone adds a second line.
Ternary
For a simple value choice, use the conditional operator:
const label = seats === 0 ? "Full" : seats < 5 ? "Few" : "Open";
Nest at most once; beyond that use if.
switch
switch compares with === and falls through to the next case unless you break or return:
switch (2) {
case 1: console.log("one");
case 2: console.log("two"); // runs
case 3: console.log("three"); // also runs: no break
break;
default: console.log("other");
}
// Output: two, three
Use switch for one variable compared to several constants; use if for ranges.
5. Loops
| Loop | Use when | Example |
|---|---|---|
for |
You need an index or a known count | for (let i = 0; i < 3; i++) |
for...of |
You want each value of an array or string | for (const p of prices) |
for...in |
You want object keys (avoid on arrays) | for (const k in obj) |
while |
Repeat until a condition changes | while (n > 0) |
do...while |
Run at least once | do { } while (cond) |
break leaves the loop; continue skips to the next iteration:
for (let i = 0; i < 5; i++) {
if (i === 1) continue; // skip 1
if (i === 4) break; // stop at 4
console.log(i);
}
// 0, 2, 3
for...in over an array yields string indices ("0", "1"), which surprises people; use for...of for arrays. Beware of infinite loops: make sure the loop variable moves toward the end condition. If a page freezes, an infinite loop is a prime suspect.
Worked example: summing prices
Task. The price list arrives from a form as strings. Compute the total.
const prices = ["12.50", "8", "3.25"];
let total = 0;
for (const p of prices) {
total += p;
}
console.log(total); // ?
Reasoning, step by step.
totalstarts as the number0.- First pass:
0 + "12.50". One operand is a string, so+concatenates:"012.50". - Second pass:
"012.50" + "8"gives"012.508". - Third pass: the result is
"012.5083.25".
Output: 012.5083.25, a string, not 23.75.
Fix. Convert each price, and work in cents to avoid float noise:
let cents = 0;
for (const p of prices) {
cents += Math.round(Number(p) * 100);
}
console.log(cents / 100); // 23.75
Why other fixes are weaker. Starting total as parseFloat(prices[0]) only patches the first iteration. Wrapping the final result in Number() is too late: the garbage string "012.5083.25" converts to NaN. Converting each input at the boundary is the correct place.
6. Scope and the temporal dead zone
Scope is where a name is visible. let and const are block-scoped: they exist only inside the nearest { }. var ignores blocks and is function-scoped.
The classic demonstration involves closures in loops:
var fns = [];
for (var i = 0; i < 3; i++) {
fns.push(() => i);
}
console.log(fns.map((f) => f())); // [3, 3, 3]
const fl = [];
for (let j = 0; j < 3; j++) {
fl.push(() => j);
}
console.log(fl.map((f) => f())); // [0, 1, 2]
With var there is a single i shared by all functions, and by the time they run it equals 3. With let, each iteration gets its own j.
Hoisting. Declarations are processed before code runs. Function declarations are fully hoisted, so you can call them above their definition. var variables are hoisted as undefined. let and const are hoisted but not initialized: reading them before their line throws a ReferenceError. That interval is the temporal dead zone (TDZ).
console.log(typeof hoisted, typeof hv); // function undefined
var hv = 1;
function hoisted() {}
try {
console.log(tdz);
let tdz = 1;
} catch (e) {
console.log(e.name, e.message);
// ReferenceError Cannot access 'tdz' before initialization
}
7. Debugging
Guessing wastes time; observe instead.
- Reproduce the bug with the smallest input.
console.logvalues and their types:console.log(typeof p, p). Useconsole.table(array)for lists andconsole.errorfor failures.- Breakpoints. In the Sources panel, click a line number; reload; execution pauses there. Use Step over (next line), Step into (enter a call), Step out. The Scope pane shows current variables and the Call stack pane shows how you got here.
- Conditional breakpoints pause only when an expression is true (
i === 2), which is ideal for loops. - Read the error.
TypeError: Cannot read properties of undefined (reading 'seats')means something before.seatswasundefined; the line number in the stack trace points at the culprit. - Insert a
debugger;statement as a code-based breakpoint, but remove it before committing.
| Error | Typical cause |
|---|---|
ReferenceError: x is not defined |
Typo, wrong scope, or TDZ |
TypeError: x is not a function |
Called something that is not a function |
TypeError: Assignment to constant variable |
Reassigned a const |
SyntaxError |
Missing bracket or quote; the file will not run at all |
8. Pitfalls and exam tips
- Pitfall:
"5" + 3is"53". Exam tip: when a question mixes+with a string, expect concatenation. - Pitfall: treating
constas "immutable value". It only prevents reassignment. - Pitfall:
||for defaults when0or""is valid. Use??. - Pitfall:
typeof null === "object"; arrays also report"object". - Pitfall: forgetting
breakinswitch, causing fall-through. - Pitfall:
for...inon arrays gives string keys. - Exam tip: to predict output, evaluate from the inside out and write each intermediate value, as in the worked example.
- Exam tip: when an option says "use
==so types match", prefer the option that converts explicitly and compares with===.
Mini case study: the booking total that says NaN
Situation. On the Harbor booking page a visitor selects the number of seats in a text field and sees the total. The code is:
const pricePerSeat = 18;
const seatsInput = document.querySelector("#seats").value; // "3"
let discount;
if (seatsInput > 4) {
discount = 0.1;
}
const total = seatsInput * pricePerSeat * (1 - discount);
console.log(total);
For 3 seats the page shows NaN. For 5 seats it shows 81.
Questions.
- Why does 3 seats give
NaN? - Why does the comparison
seatsInput > 4work althoughseatsInputis a string? - Fix the code.
Commented solution.
discountis declared with no value, so it isundefinedwhen the condition is false.1 - undefinedisNaN, andNaNpoisons the whole multiplication.>coerces the string to a number, so"5" > 4istrue. It works by accident of coercion, which hides the real problem: the input is still a string and an empty or invalid entry givesNaNor0silently.- Convert and validate at the boundary and give
discounta default:
const pricePerSeat = 18;
const seats = Number(document.querySelector("#seats").value);
if (!Number.isInteger(seats) || seats < 1) {
console.error("Enter a whole number of seats");
} else {
const discount = seats > 4 ? 0.1 : 0;
const total = seats * pricePerSeat * (1 - discount);
console.log(total.toFixed(2)); // "54.00" for 3, "81.00" for 5
}
Number("") is 0, which fails the seats < 1 check, so an empty field is rejected too. The debugging path would be to set a breakpoint on the total line and read discount in the Scope pane.
Key terms
- Primitive: an immutable basic value such as a number or string.
- Binding: the link between a name and a value.
- Coercion: automatic or explicit conversion between types.
- Truthy / falsy: how a value behaves in a condition.
- Nullish:
nullorundefined. - Block scope: visibility limited to the nearest braces.
- Temporal dead zone: the period in which a
let/constexists but cannot be read. - Breakpoint: a marker that pauses execution for inspection.
Summary checklist
- I use
constby default andletwhen I reassign; I avoidvar. - I can list the primitive types and know
typeof nullis"object". - I predict
"5" + 3and"5" - 3correctly. - I convert user input explicitly with
Number()and validate it. - I use
===, and??for defaults where0is valid. - I know the eight falsy values.
- I choose the right loop and use
break/continuedeliberately. - I can explain block scope, hoisting, and the temporal dead zone.
- I can pause code with a breakpoint and read the Scope and Call stack panes.
Official sources
- Meta Front-End Developer Professional Certificate (opens in a new tab)
- Course 2: Programming with JavaScript (opens in a new tab)
- MDN: JavaScript Guide, Grammar and types (opens in a new tab)
- MDN: Equality comparisons and sameness (opens in a new tab)
- MDN: let (opens in a new tab)
- Chrome DevTools: Debug JavaScript (opens in a new tab)
