Control flow is the order in which a program's statements actually run. By default, JavaScript reads top to bottom and left to right, one statement at a time. Everything in the last three sections (conditionals, loops, break and continue) exists to change that order. This lesson pulls it all together.
Every program, no matter how large, is built from a handful of patterns:
| Pattern | What it does | Tools |
|---|---|---|
| Sequence | Run statements in order | just write lines top to bottom |
| Selection | Choose which code runs | if, else, switch, ? : |
| Repetition | Run code more than once | for, while, do...while |
| Jumps | Leave the normal path abruptly | break, continue, return, throw |
| Functions | Run a named block when it is called | function, return |
let x = 5; // 1st
let y = 6; // 2nd
let z = x + y; // 3rd: can use x and y, because they already exist
console.log(z); // 4th
Order matters. Each statement can only use what the statements above it have already set up. Move the console.log to the top and it fails, because z does not exist yet.
Conditionals make the flow branch: one path or another, depending on a condition. Only one branch runs; the other is skipped entirely.
let age = 15;
let text = "Unknown";
if (age >= 18) {
text = "Adult"; // skipped
} else {
text = "Minor"; // runs
}
console.log(text);
Loops make the flow circle back: after the last line of the body, execution jumps back up to the condition, until the condition is false.
for (let i = 0; i < 3; i++) {
console.log("The number is " + i);
}
Four keywords jump out of the normal flow:
| break | Exit the current loop or switch | JS Break |
| continue | Skip to the next loop pass | JS Continue |
| return | Exit the current function, optionally sending back a value | below, and JS Functions |
| throw | Raise an error, jumping to error handling | JS Errors |
for (let i = 0; i < 10; i++) {
if (i === 3) { break; }
console.log("The number is " + i);
}
Here is a small program that uses sequence, selection, repetition, and jumps. It totals a day's sales, where a 0 means an empty order to skip, and a negative number means a data error that should halt processing:
const amounts = [25, 0, 40, -1, 60];
let revenue = 0;
for (const amount of amounts) {
if (amount === 0) continue; // skip empty orders
if (amount < 0) {
console.log("Stopping: bad amount");
break; // halt processing
}
revenue += amount;
}
console.log("Revenue: $" + revenue);
Trace it: 25 is added. 0 is skipped by continue. 40 is added (65 so far). -1 triggers break, so the 60 is never reached. Then the flow drops out of the loop to the final console.log.
A function is a named, reusable block of code. It does not run where it is written; it runs when it is called, and then the flow returns to where the call was made. Functions get a whole section soon (JS Functions); here is just enough to see how they change the flow:
function multiply(p1, p2) {
return p1 * p2; // send the answer back to the caller
}
console.log("before");
let result = multiply(4, 3); // flow jumps into the function, then back
console.log("after: " + result);
Reading top to bottom, the function's code appears first, but nothing happens there: it is only a definition. The flow goes "before", then jumps into multiply at the call, then comes back with 12.
return ends the function immediately, even from inside a loop. That makes it a stronger jump than break, which only exits one loop:
function firstNegative(numbers) {
for (const n of numbers) {
if (n < 0) {
return n; // leaves the loop AND the function
}
}
return null; // only reached if nothing was found
}
console.log(firstNegative([4, 9, -2, 7, -5]));
console.log(firstNegative([1, 2, 3]));
Checking for the bad or special cases first and returning early keeps the main logic flat and readable. Compare two ways of pricing a ticket:
// Nested: the real work is buried three levels deep
function ticketPrice(age) {
if (age >= 0) {
if (age >= 5) {
return 12;
} else {
return 0;
}
} else {
return null;
}
}
// Flat, with guard clauses: same results
function ticketPrice(age) {
if (age < 0) return null; // invalid age
if (age < 5) return 0; // under 5 is free
return 12; // everyone else
}
JavaScript runs on a single thread: it can only do one thing at a time. Every statement waits for the one before it to finish. That is why control flow is so predictable, and also why a slow loop is a problem:
let count = 0;
for (let i = 0; i < 3000000000; i++) {
count++; // billions of passes: takes several seconds
}
While that loop runs, the page is frozen: buttons do not respond and nothing redraws, because the single thread is busy. It is the same thing that happens with an infinite loop, just temporary.
For slow jobs like loading data from a server, JavaScript can run code asynchronously: start the job, carry on with other work, and come back when the result is ready. That is an advanced topic for much later, but it is why control flow in real web apps is not always strictly top to bottom.
if or loop, note which way it went. Most bugs are the program taking a path you did not expect.
Which statement exits a function immediately, even from inside a loop?
breakcontinuereturnelsereturn ends the whole function. break exits only the loop (the function carries on after it), and continue only skips to the next pass.In the sales example, what would revenue be if the array were [10, 0, 20, 30]?
60. The 0 is skipped, and with no negative number the loop never breaks, so 10 + 20 + 30 are all added.A button on your page stops responding while a big calculation runs. What is the reason?
firstOver100: loop over the array and use return to hand back the first number over 100 (or null if there is none)ticketPrice versions (rename the second one ticketPrice2). Call each with -3, 2, and 30 and check they give the same answers