Courses Learn Blog Contact
Get Started →
Lesson 2

Day 2: Decisions and Functions

Branching, truthy and falsy, functions, arrow syntax and scope, ending in a result analyser built from real school rules.

Webbo3 Level 2: JavaScript Development 8 min read Free

Webbo3 Level 2 · JavaScript Development · Day 2 of 20

Decisions and Functions

Teaching your code to choose, and packaging that choice into something you can use again without copying it.

By the end of today you can

  • Branch with if, else if and else, and know when switch reads better
  • Combine conditions with &&, || and ! without producing nonsense
  • Write a function that takes input and returns a result
  • Explain what scope is and why a variable "disappeared"
  • Build a small result analyser that applies real business rules

Yesterday your code ran straight down the page, doing the same thing every time. That is a calculator, not an application. An application looks at what it has been given and decides what to do about it, and it does that by asking questions whose answer is only ever true or false.

1. if, else if, else

The shape is always the same: a question in brackets, and a block that runs only when the answer is true.

JSapp.js
const score = 72;

if (score >= 70) {
  console.log('A');
} else if (score >= 60) {
  console.log('B');
} else if (score >= 50) {
  console.log('C');
} else {
  console.log('Fail');
}
Console

Order is not cosmetic here

The engine stops at the first branch that is true. Put score >= 50 at the top of that chain and every score above 50 prints "C", including a 95. When your grades all come out the same, this is why: check the order before you check anything else.

Predict: what does this print, and why is it wrong?
JSapp.js
const score = 95;

if (score >= 50) { console.log('C'); }
else if (score >= 70) { console.log('B'); }
else if (score >= 90) { console.log('A'); }

It prints C. 95 is indeed greater than 50, that branch is checked first, it wins, and the rest is never looked at. Broadest condition last, narrowest first.

2. Comparison and logic

OperatorMeansExample
===Equal, type included5 === 5 is true
!==Not equal'a' !== 'b' is true
> <Greater, less70 > 50 is true
>= <=Greater or equal50 >= 50 is true
&&AND: both must be truea && b
\|\|OR: either may be truea || b
!NOT: flips it!true is false
JSapp.js
const score = 78;
const attendance = 0.6;

const passedTest = score >= 50;
const attendedEnough = attendance >= 0.75;

console.log('passed test:', passedTest);
console.log('attended enough:', attendedEnough);
console.log('eligible:', passedTest && attendedEnough);
console.log('needs review:', passedTest && !attendedEnough);
Console

Notice what that code does with names. passedTest and attendedEnough are not necessary: the whole thing would fit on one line. But the one-line version says if (score >= 50 && attendance >= 0.75), and the named version says what those numbers mean. Naming a condition is the cheapest readability win in programming, and it is the habit that separates code a team can maintain from code only its author can read.

3. Truthy, falsy, and the empty-field trap

JavaScript will accept any value where a condition is expected, and quietly decide whether it counts as true. A short list of values are falsy. Everything else in the language is truthy, including some things that look empty.

JSapp.js
// the falsy values you will actually meet
const falsy = [false, 0, '', null, undefined, NaN];
for (const value of falsy) {
  console.log(String(value).padEnd(10), '->', Boolean(value));
}

console.log('--- but these are TRUE ---');
console.log('"0"     ->', Boolean('0'));    // a string, not the number zero
console.log('"false" ->', Boolean('false'));
console.log('[]      ->', Boolean([]));     // an empty array still exists
Console

The bug this creates

A user leaves a quantity box empty and you check if (!quantity). Empty string is falsy, so you correctly reject it. Then a user types 0 and you reject that too, because 0 is also falsy, even though zero was a perfectly valid thing to type. When "empty" and "zero" must mean different things, test for what you actually mean: quantity === ''.

4. switch, when there are many exact matches

switch is not a different kind of logic, it is a tidier shape for one specific case: one value compared against several exact possibilities.

JSapp.js
const day = 'saturday';

switch (day) {
  case 'saturday':
  case 'sunday':
    console.log('Weekend rate');
    break;
  case 'friday':
    console.log('Friday rate');
    break;
  default:
    console.log('Weekday rate');
}
Console

Forgetting break is the classic switch bug

Without break, execution falls straight through into the next case and runs that too. Above, the missing break after case 'saturday' is deliberate: it is how two values share one outcome. Everywhere else, a missing break is an accident.

5. Functions: the real unit of work

A function is a named, reusable block that takes values in and hands a value back. Once you can write one, you stop copying code and start calling it.

JSapp.js
function gradeFor(score) {
  if (score >= 70) return 'A';
  if (score >= 60) return 'B';
  if (score >= 50) return 'C';
  return 'F';
}

console.log(gradeFor(95), gradeFor(64), gradeFor(31));
Console

Look at what return did to the shape. Because return exits the function on the spot, the else branches disappeared entirely. That pattern, called an early return, flattens nested code and is worth reaching for deliberately.

Arrow functions

The same thing, written shorter. You will see both everywhere, so you need to read both.

JSapp.js
const double = function (n) { return n * 2; };   // function expression
const triple = (n) => n * 3;                     // arrow, implicit return
const areaOf = (w, h) => w * h;

console.log(double(4), triple(4), areaOf(3, 5));
Console

An arrow with no braces returns its single expression automatically. Add braces and you must write return yourself. That is the trap:

JSapp.js
const withBraces = (n) => { n * 2; };   // no return written
console.log(withBraces(4));
Console

It prints undefined, because the function ran and returned nothing. A function with no return returns undefined, which then travels quietly through the rest of your program until it breaks something far away from the real cause.

6. Parameters, arguments and defaults

JSapp.js
// vat defaults to 7.5 when the caller does not supply it
function totalWithVat(amount, vat = 7.5) {
  return amount + (amount * vat / 100);
}

console.log(totalWithVat(1000));
console.log(totalWithVat(1000, 0));
Console

That second call matters. Because the default only fills in for undefined, passing 0 genuinely means zero rather than falling back to 7.5. If you had written the fallback as vat = vat || 7.5 instead, passing 0 would have given you 7.5, because 0 is falsy. Default parameters are the safe way to do this.

7. Scope: why your variable disappeared

A variable declared with let or const inside { } exists only inside those braces. Outside them it does not exist at all.

JSapp.js
function checkIn(name) {
  const greeting = `Welcome, ${name}`;
  if (name.length > 3) {
    const note = 'long name';
    console.log(greeting, '-', note);
  }
  // `note` does not exist out here
  return greeting;
}

console.log(checkIn('Adewale'));
Console
JSapp.js
if (true) {
  const inside = 'only here';
}
console.log(inside);
Console

That error message is one you will meet often, and it almost always means the same thing: you declared the variable inside a block and tried to use it outside. Declare it one level up, before the block, and assign inside.

Do this now: the Student Result Analyzer

This is your first piece of real business logic: rules a school actually has, expressed in code. Build it as functions, not as one long script.

  1. A function average(scores) that takes an array of three scores and returns the average.
  2. A function gradeFor(average) that returns A, B, C or F on the boundaries above.
  3. A function verdict(average, attendance) that returns Pass only when the average is 50 or more and attendance is 0.75 or more, Withheld when the average passes but attendance does not, and Fail otherwise.
  4. A function report(student) that puts it together and returns a formatted multi-line string using a template literal.
JSapp.js
const average = (scores) => scores.reduce((sum, n) => sum + n, 0) / scores.length;

function gradeFor(avg) {
  if (avg >= 70) return 'A';
  if (avg >= 60) return 'B';
  if (avg >= 50) return 'C';
  return 'F';
}

function verdict(avg, attendance) {
  const passed = avg >= 50;
  const attended = attendance >= 0.75;
  if (passed && attended) return 'Pass';
  if (passed && !attended) return 'Withheld: attendance below 75%';
  return 'Fail';
}

function report(student) {
  const avg = average(student.scores);
  return `${student.name} | avg ${avg.toFixed(1)} | ${gradeFor(avg)} | ${verdict(avg, student.attendance)}`;
}

console.log(report({ name: 'Chidinma', scores: [72, 65, 88], attendance: 0.9 }));
console.log(report({ name: 'Musa', scores: [55, 61, 58], attendance: 0.5 }));
console.log(report({ name: 'Tunde', scores: [30, 44, 39], attendance: 0.95 }));
Console

Extend it tonight

Add a fourth rule of your own and make it fit the existing functions without rewriting them. If adding a rule forces you to change three functions, your functions are doing too much each: that feeling is the lesson.

Checkpoint

Q1. Why did the grade chain have to start at 70 rather than 50?

Because the first true branch wins. Starting at 50 catches every passing score in the first branch and the higher grades are never reached.

Q2. A function has no `return`. What do you get when you call it?

undefined. It ran, it may have printed things, but it handed nothing back. An arrow function with braces and no return is the usual way this happens by accident.

Q3. Why is `vat = 7.5` as a default parameter safer than `vat || 7.5`?

A default parameter only fills in for undefined. || fills in for every falsy value, so a genuine 0 would be replaced by 7.5.

Q4. Name a value that is truthy but that a beginner expects to be falsy.

The string "0", the string "false", and an empty array []. All three are truthy, because they are non-empty strings and an existing object.

Tick before you move on

  • I wrote the analyser as four separate functions, not one script
  • I hit the ReferenceError from block scope and understood it
  • I used early returns instead of nested else branches
  • All three of my test students produced the right verdict

Quick recap

First true branch wins, so order the chain narrowest first · Name your conditions: passedTest reads better than score >= 50 · Eight falsy values, and 0 being one of them is the empty-field trap · A function with no return gives back undefined · let and const live only inside the braces they were declared in

Tomorrow, Day 3: arrays and objects. Real applications are mostly data being reshaped, and tomorrow is the day you learn to reshape it.

Learn this with a real instructor

Free lessons take you a long way on your own. The full programmes add live classes, assignments, Skill Points and a certificate — built for African students.