New: Try Voli The Bear, Fast package manager (and not only) for Windows
Updated Jun 19, 2026 Edit on GitHub

Control Flow & Functions

So far your code runs straight down, every line, once. Real programs need to choose (do this only if that's true), repeat (do this for every item), and reuse behavior without copy-pasting. Those three needs - decisions, loops, and functions - are this phase. Functions especially: the unit you'll think in for the rest of your career.

Making decisions: if / else

if runs a block of code only when a condition is true; else covers the other case.

const hour = 14;

if (hour < 12) {
  console.log("Good morning");
} else if (hour < 18) {
  console.log("Good afternoon");
} else {
  console.log("Good evening");
}
Good afternoon

What just happened: JavaScript checked the conditions top to bottom. hour < 12 was false (14 isn't less than 12), so it moved on; hour < 18 was true, so it ran that block and skipped the rest. The first matching branch wins - { } braces group the lines in each branch.

Here's that decision as a picture:

📝 Terminology - truthy and falsy. Conditions don't have to be literal true/false - JavaScript treats some values as "truthy," others "falsy," in a yes/no context. Falsy values are worth memorizing since they're a common source of bugs: false, 0, "" (empty string), null, undefined, NaN. Everything else is truthy - including "0" (a non-empty string) and [] (an empty array). So if (value) means "if value is truthy."

Repeating: for...of and while

The cleanest way to do something with every item in an array is for...of:

const names = ["Ada", "Linus", "Grace"];
for (const name of names) {
  console.log(`Hello, ${name}`);
}
Hello, Ada
Hello, Linus
Hello, Grace

What just happened: for (const name of names) walked names, putting each item into name and running the block - no index counter to manage. (You'll also see the older C-style for (let i = 0; i < names.length; i++) loop; for...of is cleaner when you don't need the index.)

When you don't know how many times to loop in advance, use while - it repeats as long as its condition stays true:

let countdown = 3;
while (countdown > 0) {
  console.log(countdown);
  countdown = countdown - 1;
}
console.log("Liftoff!");
3
2
1
Liftoff!

What just happened: while checked countdown > 0, ran the block, and checked again, repeating until countdown hit 0. Crucially, the block changes countdown each time. ⚠️ A while loop whose condition never becomes false (you forget to change the thing it checks) runs forever and freezes your program - the classic "infinite loop." Make sure each pass moves toward the exit.

Functions: naming a block of behavior

A function is a named, reusable block of instructions taking inputs (parameters) and handing back an output (a return value) - write behavior once, run it whenever you need it, with different inputs.

function greet(name) {
  return `Hello, ${name}!`;
}

console.log(greet("Ada"));
console.log(greet("Grace"));
Hello, Ada!
Hello, Grace!

What just happened: function greet(name) { ... } defined a function with one parameter, name. return hands a value back to the caller. greet("Ada") produced "Hello, Ada!"; same function, called twice with different inputs, gave two different outputs - the whole point of parameters.

📝 Terminology. A parameter is the name in the function definition (name); an argument is the value passed in when calling ("Ada"). return ends the function and sends a value back; a function with no return hands back undefined.

Default parameters let a parameter fall back to a value if the caller leaves it out:

function greet(name = "friend") {
  return `Hello, ${name}!`;
}
console.log(greet());          // no argument passed
console.log(greet("Ada"));
Hello, friend!
Hello, Ada!

What just happened: Calling greet() with no argument let name fall back to its default, "friend"; passing "Ada" overrode it. Defaults save you from scattering "if it wasn't provided, use X" checks through your code.

Arrow functions: the compact form

You've already seen these in Phase 3. An arrow function is a shorter way to write a function, used constantly for small inline functions:

const double = (n) => n * 2;
const greet = (name) => `Hello, ${name}!`;

console.log(double(5));
console.log(greet("Ada"));
10
Hello, Ada!

What just happened: (n) => n * 2 is a function taking n and returning n * 2. A single-expression body skips the { } and the word return - the value returns automatically. Same idea as a function, written tighter. For a multi-line body, bring back the braces and an explicit return: (n) => { const r = n * 2; return r; }.

For now, treat arrow functions and function declarations as two ways to write the same thing. One real behavioral difference around this is flagged at the end of this phase and covered fully later.

Functions are values you can pass around

The idea that makes JavaScript click: a function is itself a value - you can store it in a variable, put it in an array, and (the powerful part) pass it to another function. A function passed to another function is called a callback.

function runTwice(action) {
  action();
  action();
}

runTwice(() => console.log("tick"));
tick
tick

What just happened: runTwice takes a function as its argument and calls it twice. We handed it () => console.log("tick"), and it ran twice. This is exactly what map/filter/reduce do: hand them a function, and they decide when and how to call it on your data. Once this feels natural, huge swaths of JavaScript (event handlers, array methods, async code) stop looking like magic.

💡 Key point. "First-class functions" is the formal name for this: functions are values, equal citizens with numbers and strings. Passing behavior into other code, not just data, is the backbone of how JavaScript handles clicks, timers, and network responses - all coming in Phase 6.

⚠️ A tease: this depends on how you call a function

You'll eventually meet the keyword this inside functions - one of JavaScript's genuinely confusing corners. The sentence that defuses most of the pain: this is not set by where a function is defined, but by how it is called. The same function sees a different this depending on whether you call it as a method, on its own, or as a callback. Arrow functions don't get their own this - one reason people prefer them for callbacks.

That's all for now; the full mental model gets its own treatment in Phase 9: Idioms & Gotchas.

Recap

  1. if / else if / else picks the first branch whose condition is truthy; remember the falsy values (false, 0, "", null, undefined, NaN).
  2. for...of loops over each item in a list cleanly; while repeats until its condition goes false - make sure it eventually does.
  3. Functions package reusable behavior: parameters are inputs, return is the output, and defaults cover missing arguments.
  4. Arrow functions ((n) => n * 2) are the compact form for small inline functions.
  5. Functions are values - you can pass them around; one passed into another function is a callback. And this is decided by how a function is called, not where it's written.

Next: modules, which let you split a program across files and pull the pieces together cleanly.


← Phase 3: Collections · Guide overview · Phase 5: Modules & Project Layout →

Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.

Check your understanding 2 questions

1. A 'callback' is...

2. In JavaScript, the falsy values are...