Until now your code ran in a straight line: from the first statement to the last, never taking a detour. That is enough to describe a Taller Nómada task, but not to reason about it. Marta does not want the program to repeat back what she already knows: she wants it to tell her which of her tasks are at risk, whether Iván can mark a task as done, or whether the creation form contains an invalid value. All of those are decisions, and a decision in JavaScript is written with a conditional statement. In this lesson you will learn to branch the flow with if, else and else if, to build compound conditions that read well, to keep your code flat instead of nesting it until it becomes unreadable, and to decide when a ternary improves the code and when it makes it worse.
Contents
- From straight-line flow to branching
- The
ifstatement else: the alternative pathelse if: chains of decisions- Compound conditions with
&&,||and! - Parentheses: the difference between correct and readable
- Conditions on truthy and falsy values
- The nesting problem
- Guard clauses and early exit
- The ternary operator and its limits
- Case study: classifying a task's urgency
- Case study: validating a status change (R6)
- A decision table for the business rules
- Common Mistakes and Tips
- Exercises
- Conclusion
- From straight-line flow to branching
In JavaScript Syntax and Basic Concepts you saw that JavaScript executes statements top to bottom, one after another. A control structure breaks that linearity: it lets certain statements run only under specific circumstances, or run many times.
Control structures fall into two families:
| Family | What it does | Statements |
|---|---|---|
| Selection | Picks one path out of several | if, else, else if, switch |
| Iteration | Repeats a block of statements | for, while, do...while |
This lesson covers selection with if. The switch arrives in Switch Statements and iteration in Loops: for, while, do-while.
- The
if statement
if statementThe simplest form: a condition and a block that runs only if that condition is true.
const estimatedHours = 46;
if (estimatedHours > 40) {
console.warn('The task exceeds a full working week.');
}
console.log('Check finished.');Anatomy, piece by piece:
ifis the reserved word. It is not strictly required to sit next to the parenthesis, but it is always written asif (.(estimatedHours > 40)is the condition. The parentheses are mandatory. Inside goes any expression that produces a value; JavaScript converts it to a boolean exactly asBoolean()did in Type Conversion and Comparisons.{ ... }is the block that runs if the condition is true. Withletandconst, anything declared inside exists only inside.- There is no
;after the closing brace. Anifis a block statement, not an expression.
The output of the example:
If you change 46 to 12, the warning does not appear and only the second line is printed. The program chose not to run a piece of code.
2.1 The braces are optional, but always write them
JavaScript lets you drop the braces when the body is a single statement:
It works, but it is a classic source of bugs. Look at what happens when you add a second line:
// ✗ TRAP: the second line ALWAYS runs
if (estimatedHours > 40)
console.warn('It exceeds 40 hours.');
console.warn('Review it with Marta.');The indentation suggests that both lines belong to the if, but indentation means nothing in JavaScript. Without braces, the if governs the first statement only; the second is ordinary code that always runs. The bug is invisible when you read it and obvious when you run it.
Course rule: always use braces, even when the body is a single line. It costs nothing and removes an entire category of bugs.
else: the alternative path
else: the alternative pathelse defines what to do when the condition is false. It has no condition of its own: it means "in any other case".
const status = 'in-progress';
if (status === 'done') {
console.log('Task completed. No follow-up needed.');
} else {
console.log('Task open. Still on the board.');
}Exactly one of the two blocks runs, never both and never neither.
flowchart TD
A["status === 'done'"] -->|true| B["Task completed"]
A -->|false| C["Task open"]
B --> D["The program continues"]
C --> D
else if: chains of decisions
else if: chains of decisionsWhen there are more than two paths, conditions are chained with else if. Turning a task's priority into a numeric weight is the project's canonical example:
const priority = 'medium';
let weight;
if (priority === 'high') {
weight = 3;
} else if (priority === 'medium') {
weight = 2;
} else if (priority === 'low') {
weight = 1;
} else {
weight = 0;
console.error(`Unknown priority: "${priority}"`);
}
console.log(`Task weight: ${weight}`); // Task weight: 2Three important details:
- The conditions are evaluated in order and the chain stops at the first true one. If
priorityis'high', the other two comparisons never even run. Order matters. - The final
elseis the safety net. It catches the values you were not expecting: a'HIGH'in uppercase, anundefined, a typo. Without it,weightwould stayundefinedand the failure would surface much later, somewhere else, disguised asNaN. let weight;goes before theif. If you declaredconst weight = 3;inside the block, that variable would exist only inside that block and would vanish on the way out. Declaring outside and assigning inside is the usual pattern.
Technically else if is not a new construct: it is an else whose body is another if. It is written on the same line by convention, so that the chain reads as a list of cases rather than a staircase.
- Compound conditions with
&&, || and !
&&, || and !In Basic Operators you learned the logical operators and their short-circuiting. Inside an if is where they show their full value.
| Operator | Read as | It is true when |
|---|---|---|
&& |
"and" | Both sides are true |
|| |
"or" | At least one of the sides is true |
! |
"not" | The value on the right is false |
Rule R10 of the project says: an overdue task (dueDate in the past and a status other than 'done') is highlighted. Those are two conditions that must hold at the same time, so they are joined with &&:
const TODAY = '2026-09-20';
const title = 'Carpentry workshop quote';
const dueDate = '2026-09-05';
const status = 'pending';
if (dueDate < TODAY && status !== 'done') {
console.error(`OVERDUE: ${title} (was due on ${dueDate})`);
}Remember from The Course Project why dueDate < TODAY works without conversions: the ISO format 'yyyy-mm-dd' sorts alphabetically the same way it sorts chronologically.
With || we express "any of these situations justifies reviewing the task":
const assignee = null;
const estimatedHours = 44;
if (assignee === null || estimatedHours > 40) {
console.warn(`"${title}" needs a review from Marta.`);
}And ! inverts a condition. These two lines are equivalent:
The second one is clearly better. Use ! to negate boolean variables, not to negate comparisons: that is what !==, <= and >= are for.
const isArchived = false;
if (!isArchived) {
console.log('The task is still visible on the board.');
}
- Parentheses: the difference between correct and readable
&& has higher precedence than ||, just as * has higher precedence than +. That means this condition:
groups like this:
That may be what you meant, or it may not. The problem is that whoever reads the code cannot tell. Compare it with the other possible grouping:
With priority = 'high' and estimatedHours = 5, the first version gives true and the second false. They are different conditions that look very much alike.
Rule: as soon as a condition mixes && and ||, write the parentheses even though precedence already does the right thing. They are not for the engine: they are for the person who reviews the code six months from now.
When a condition grows too large, the best tool is not more parentheses but named boolean variables:
const TODAY = '2026-09-20';
const priority = 'high';
const status = 'pending';
const dueDate = '2026-09-30';
const estimatedHours = 12;
const isOverdue = dueDate < TODAY && status !== 'done';
const isImportant = priority === 'high' || estimatedHours > 20;
const isOpen = status !== 'done';
if (isOpen && (isOverdue || isImportant)) {
console.log("This task goes into Marta's daily review.");
}The final if now reads like a plain English sentence: it is open, and it is either overdue or important. Each intermediate variable documents one business idea, and you can print it separately when something does not add up. This technique —giving names to the pieces of a condition— is the single biggest readability win in real code.
- Conditions on truthy and falsy values
In Type Conversion and Comparisons you saw the eight falsy values: false, 0, -0, 0n, '', null, undefined and NaN. Everything else is truthy. An if applies exactly that conversion to its condition.
This lets you write very short checks:
const reviewer = null;
if (reviewer) {
console.log(`Reviewer: ${reviewer}`);
} else {
console.log('Task with no reviewer assigned.');
}Convenient, but dangerous, because if (value) lumps together very different situations. Watch what happens with the project's fields:
| Check | 0 |
'' |
null |
undefined |
[] |
'0' |
|---|---|---|---|---|---|---|
if (value) |
✗ skipped | ✗ skipped | ✗ skipped | ✗ skipped | ✓ entered | ✓ entered |
Two direct consequences for Nómada Tasks:
estimatedHours can be a valid number and still be falsy. If one day 0-hour tasks were allowed, if (estimatedHours) would treat them as if the field were missing. The honest check is explicit:
const estimatedHours = 0;
if (estimatedHours > 0 && estimatedHours <= 40) {
console.log('Valid hours.');
} else {
console.error('R3 violated: hours must be between 0 (exclusive) and 40.');
}An empty array is truthy. if (tags) gives true even when there is not a single tag, because the array exists. Since tags is always an array (never null), what you have to look at is its length:
const tags = [];
if (tags.length === 0) {
console.log('Task with no tags.');
} else {
console.log(`It has ${tags.length} tag(s).`);
}Practical rule: use if (value) only when you genuinely mean "there is something usable here" and none of the falsy values is legitimate for that field. In every other case, write the full comparison: === null, > 0, .length === 0, !== ''. The code gains in clarity what it loses in brevity.
- The nesting problem
An if can contain another if. The first time you need it, the result looks reasonable:
// ✗ Hard to follow
const status = 'pending';
const assignee = 'Iván';
const dueDate = '2026-09-30';
const TODAY = '2026-09-20';
if (status !== 'done') {
if (assignee !== null) {
if (dueDate < TODAY) {
console.error('Overdue and assigned.');
} else {
console.log('Assigned and on schedule.');
}
} else {
console.warn('Open but with no assignee.');
}
} else {
console.log('Finished.');
}This code works, but it has three serious problems:
- The important case is buried. To work out when "Overdue and assigned" gets printed you have to mentally reconstruct three accumulated conditions.
- The
elsebranches are far from theirif. Theelseon the last lines belongs to the firstif, twenty lines further up. - Every new level multiplies the paths. Three levels already give eight possible combinations.
This shape is known as arrow code, because the indentation draws an arrowhead pointing right. It is one of the most reliable signs that a piece of logic needs rewriting.
- Guard clauses and early exit
The alternative is the guard clause: instead of nesting the good case inside successive checks, you discard the exceptional cases first and leave the main path at the end, unindented.
We rewrite the previous example as a flat chain of else if, ordered from the most exclusive case to the most general:
const status = 'pending';
const assignee = 'Iván';
const dueDate = '2026-09-30';
const TODAY = '2026-09-20';
let message;
if (status === 'done') {
message = 'Finished.';
} else if (assignee === null) {
message = 'Open but with no assignee.';
} else if (dueDate < TODAY) {
message = 'Overdue and assigned.';
} else {
message = 'Assigned and on schedule.';
}
console.log(message);Same behavior, a single level of indentation and four cases that read like a list. Each line answers one question and the chain stops at the first affirmative answer, so the later conditions can take everything above them for granted: by the time dueDate < TODAY is evaluated, we already know for certain that the task is not done and that it has an assignee.
A second pattern, very useful in validations, is to accumulate the reason for rejection in a variable:
const title = ' ';
const estimatedHours = 52;
let error = '';
if (title.trim() === '') {
error = 'R2: the title cannot be empty.';
} else if (estimatedHours <= 0) {
error = 'R3: hours must be greater than 0.';
} else if (estimatedHours > 40) {
error = 'R3: hours cannot exceed 40.';
}
if (error !== '') {
console.error(`Task rejected. ${error}`);
} else {
console.log('Task accepted.');
}title.trim() removes the whitespace at both ends, so a title made only of spaces is detected as empty: exactly what rule R2 requires.
A note about the name. "Early exit" describes leaving a block of logic immediately as soon as an invalid case is detected. Its canonical form uses return inside a function, and that tool arrives in Defining and Calling Functions. In the meantime, the flat else if chain gives you the same main benefit —zero nesting and ordered cases— with what you already know. Once you know functions, this same code will turn into three lines with return and it will feel natural.
- The ternary operator and its limits
The ternary condition ? valueIfTrue : valueIfFalse you saw in Basic Operators is an expression: it produces a value. That is the key to knowing when to use it.
Use it to assign or interpolate a value, where it shines:
const assignee = null;
const status = 'done';
const estimatedHours = 12;
const who = assignee === null ? 'unassigned' : assignee;
const icon = status === 'done' ? '✓' : '○';
console.log(`${icon} Redesign the multipurpose room · ${who} · ${estimatedHours} h`);
// ✓ Redesign the multipurpose room · unassigned · 12 hWith an if/else you would need five lines and a let variable that never changes again afterwards. The ternary lets you keep const, which is always preferable.
Do not use it to run actions. This works but it is an abuse: the ternary is designed to produce values, not to pick side effects.
// ✗ Abuse: the return value is used for nothing
status === 'done' ? console.log('Done') : console.error('Pending');
// ✓ An if/else expresses exactly what happens
if (status === 'done') {
console.log('Done');
} else {
console.error('Pending');
}Do not nest it more than once. A ternary inside another is still readable if you format it in a column, but three are not:
// ✗ Unreadable
const weight = priority === 'high' ? 3 : priority === 'medium' ? 2 : priority === 'low' ? 1 : 0;
// △ Acceptable if it is aligned, but an if/else if is clearer
const alignedWeight =
priority === 'high' ? 3 :
priority === 'medium' ? 2 :
priority === 'low' ? 1 :
0;When the chain of cases grows, the right construct is an if/else if or a switch, which you will see in lesson 02-03.
| Situation | Recommended tool |
|---|---|
| Assigning one of two values | Ternary |
| Choosing a piece of text inside a template literal | Ternary |
| Running different statements depending on the case | if / else |
| Three or more cases on the same variable | if / else if or switch |
A condition mixing && and || |
if with named boolean variables |
- Case study: classifying a task's urgency
Marta needs an urgency label that combines two signals: the priority and the days left before the due date. This is the project's first real piece of business logic.
const TODAY = '2026-09-20';
const title = 'Redesign the multipurpose room';
const priority = 'high';
const status = 'in-progress';
const dueDate = '2026-09-30';
// Days left: we convert the ISO dates to milliseconds and subtract.
const MS_PER_DAY = 1000 * 60 * 60 * 24;
const daysLeft = Math.round((new Date(dueDate) - new Date(TODAY)) / MS_PER_DAY);
// Business signals, each one with its own name
const isClosed = status === 'done';
const isOverdue = daysLeft < 0;
const isImminent = daysLeft >= 0 && daysLeft <= 3;
const isHighPriority = priority === 'high';
let urgency;
if (isClosed) {
urgency = 'none';
} else if (isOverdue) {
urgency = 'critical';
} else if (isImminent && isHighPriority) {
urgency = 'critical';
} else if (isImminent || isHighPriority) {
urgency = 'high';
} else if (daysLeft <= 14) {
urgency = 'medium';
} else {
urgency = 'low';
}
console.log(`${title}: ${urgency} urgency (${daysLeft} days left)`);
// Redesign the multipurpose room: high urgency (10 days left)What you need to understand here:
new Date(dueDate) - new Date(TODAY)takes advantage of something you already know: when the-operator is used, JavaScript converts each date to its number of milliseconds since 1970. Subtracting them gives the distance in milliseconds; dividing byMS_PER_DAYturns it into days. It is the only place in the project where we useDate, exactly as announced in lesson 01-08.- The order of the chain is business logic, not whim. A task that is done is never urgent even if it is past its due date, so
isClosedcomes first. An overdue one is critical with no further checks, so it comes second. - The four boolean variables make the chain self-explanatory. Without them, the fourth condition would be
(daysLeft >= 0 && daysLeft <= 3) || priority === 'high', correct but opaque.
- Case study: validating a status change (R6)
Rule R6 says that only the life-cycle transitions are valid. Remember the diagram from lesson 01-08:
stateDiagram-v2
[*] --> pending: it is created
pending --> in_progress: someone starts it
in_progress --> done: it is finished
in_progress --> pending: it is parked
done --> in_progress: it needs touching up
Translated into conditions: four transitions are valid and no others. Iván tries to move a task straight from 'pending' to 'done', and the program has to reject it with an understandable reason.
const currentStatus = 'pending';
const newStatus = 'done';
const IS_VALID_STATUS =
newStatus === 'pending' || newStatus === 'in-progress' || newStatus === 'done';
let allowed;
let reason;
if (!IS_VALID_STATUS) {
allowed = false;
reason = `"${newStatus}" is not a status of the system.`;
} else if (currentStatus === newStatus) {
allowed = false;
reason = `The task is already in status "${currentStatus}".`;
} else if (currentStatus === 'pending' && newStatus === 'in-progress') {
allowed = true;
reason = 'Someone starts the task.';
} else if (currentStatus === 'in-progress' && newStatus === 'done') {
allowed = true;
reason = 'Task finished.';
} else if (currentStatus === 'in-progress' && newStatus === 'pending') {
allowed = true;
reason = 'The task is parked.';
} else if (currentStatus === 'done' && newStatus === 'in-progress') {
allowed = true;
reason = 'It is reopened for touch-ups.';
} else {
allowed = false;
reason = `Transition not allowed: ${currentStatus} → ${newStatus}.`;
}
if (allowed) {
console.log(`✓ ${reason}`);
} else {
console.error(`✗ ${reason}`);
}
// ✗ Transition not allowed: pending → done.Notice the structure: two guards at the start (non-existent status, repeated status), four permitted cases in the middle and an else that rejects everything else. Rejecting by default is the right policy in a validation: if tomorrow someone adds a new status and forgets its transition, the system will reject it instead of accepting it silently.
This chain of six else if is long, and there is a reason: each branch compares two variables. In lesson 02-03 you will see that a switch does not improve this particular case, and in Module 4 you will solve it with a lookup object in four lines. For now, this version is explicit and correct, which is what matters.
- A decision table for the business rules
Before writing a complicated if it pays to draw a decision table: one row per case, one column per condition and one column with the result. Here is the one for the validations Nómada Tasks applies when accepting a new task.
| # | Condition | Rule | Result if violated |
|---|---|---|---|
| 1 | title.trim() !== '' |
R2 | Reject: empty title |
| 2 | title.length <= 100 |
R2 | Reject: title too long |
| 3 | estimatedHours > 0 |
R3 | Reject: hours not positive |
| 4 | estimatedHours <= 40 |
R3 | Reject: exceeds the working week |
| 5 | dueDate >= TODAY |
R4 | Reject: due date in the past |
| 6 | priority ∈ {high, medium, low} |
— | Reject: unknown priority |
| 7 | status === 'pending' |
R5 | Reject: a new task is born pending |
| 8 | assignee is null or on the team |
R8 | Reject: unrecognized assignee |
The table translates almost mechanically into a chain of guards: each row is an else if that assigns the rejection message. Writing it before you program has two advantages: you discover forgotten cases while you fill it in, and it gives you the list of tests you will run in Module 8.
Common Mistakes and Tips
Using = instead of == or ===. The classic mistake.
= assigns, it does not compare. The expression evaluates to 'done', which is truthy, so the condition always holds and on top of that you have destroyed the original value. With const you will get a TypeError that saves you; with let, a silent bug. Always read === out loud as "is equal to" and = as "gets".
Comparing with == out of habit. '0' == false is true and null == 0 is false. Always use ===, as was established in lesson 01-07.
Putting a ; after the condition.
The ; is the empty body of the if, and the block that follows is a loose block that runs no matter what. There is no error and no warning.
Chaining comparisons the way you would in math. if (0 < hours < 40) does not do what it looks like: it evaluates 0 < hours, gets true or false, and compares that with 40 after converting it to 1 or 0. It always gives true. The correct form is hours > 0 && hours < 40.
Forgetting the final else. If an else if chain assigns a variable, without an else that variable can be left as undefined. Always add a default case, even if it only logs an error.
Not repeating the variable in each comparison. priority === 'high' || 'medium' does not compare against 'medium': it evaluates priority === 'high' and, if that is false, returns 'medium', which is truthy. The condition always holds. You have to write priority === 'high' || priority === 'medium'.
Tip: turn long conditions into named booleans. It is the technique from this lesson with the biggest impact on real code.
Tip: order your chains from the most specific case to the most general. If the general case goes first, the specific ones are never reached.
Exercises
Exercise 1 — Workload label
Marta wants to know at a glance whether a task is manageable. Write an if/else if chain that, from estimatedHours, assigns one of these values to a variable workload, and rejects with an error message the values that violate rule R3:
| Hours | workload |
|---|---|
| Less than or equal to 0, or greater than 40 | 'invalid' |
| From 1 to 4 | 'light' |
| From 5 to 15 | 'medium' |
| From 16 to 40 | 'heavy' |
Test it with 2, 12, 30 and -3.
Exercise 2 — The task traffic light
With these variables, compute a variable trafficLight with the value '🔴', '🟠' or '🟢' according to these rules, using named boolean variables so that the final if reads like a sentence:
- 🔴 if the task is not done and its due date has already passed.
- 🟠 if it is not done, the date has not passed and the priority is
'high'. - 🟢 in every other case.
const TODAY = '2026-09-20';
const title = 'Update the bookings website';
const status = 'pending';
const priority = 'high';
const dueDate = '2026-10-02';Then print a line with the traffic light and the title. Solve the final part (choosing the text that goes with the traffic light: 'urgent', 'attention' or 'on schedule') with a ternary where that is reasonable.
Exercise 3 — Who can change the status
Extend the validation from section 12 with an additional Taller Nómada rule: only the task's assignee or Marta (the coordinator) can change its status. Add the variables assignee and user, and make the validation reject the change with a clear reason when the user has no permission. Be careful with the order: checking permission has to come before checking the transition, because it makes no sense to explain that the transition is invalid to someone who could not touch the task in the first place.
Test it with: assignee 'Iván' and user 'Lucía' (it must reject on permission), and assignee 'Iván' and user 'Marta' with the transition in-progress → done (it must allow it).
Solutions
Exercise 1
const estimatedHours = 12;
let workload;
if (estimatedHours <= 0 || estimatedHours > 40) {
workload = 'invalid';
console.error(`R3 violated: ${estimatedHours} h is not an acceptable value.`);
} else if (estimatedHours <= 4) {
workload = 'light';
} else if (estimatedHours <= 15) {
workload = 'medium';
} else {
workload = 'heavy';
}
console.log(`Workload: ${workload}`); // Workload: mediumThe key is to discard the invalid values first. Once that guard is passed, we know that estimatedHours is between 0 (exclusive) and 40, so the following conditions only need the upper bound: <= 4, <= 15 and the else. There is no need to write estimatedHours >= 5 && estimatedHours <= 15, because reaching that branch already guarantees it is greater than 4. That implicit accumulation is the great advantage of else if chains over separate if statements.
With -3: it enters the first branch, workload is 'invalid' and the error is logged. With 2: 'light'. With 30: 'heavy'.
Exercise 2
const TODAY = '2026-09-20';
const title = 'Update the bookings website';
const status = 'pending';
const priority = 'high';
const dueDate = '2026-10-02';
const isOpen = status !== 'done';
const hasExpired = dueDate < TODAY;
const isHighPriority = priority === 'high';
let trafficLight;
if (isOpen && hasExpired) {
trafficLight = '🔴';
} else if (isOpen && isHighPriority) {
trafficLight = '🟠';
} else {
trafficLight = '🟢';
}
const note = trafficLight === '🔴' ? 'urgent' : trafficLight === '🟠' ? 'attention' : 'on schedule';
console.log(`${trafficLight} ${title} — ${note}`);
// 🟠 Update the bookings website — attentionThe three boolean variables turn the if into something you read straight off: "if it is open and it has expired...". Without them, the first condition would be status !== 'done' && dueDate < TODAY, which forces you to read it twice.
The double ternary for note is at the limit of what is acceptable: three cases on a single variable, visually aligned. If there were a fourth traffic light, the right answer would be an if/else if or the switch from the next lesson.
Exercise 3
const currentStatus = 'in-progress';
const newStatus = 'done';
const assignee = 'Iván';
const user = 'Marta';
const IS_VALID_STATUS =
newStatus === 'pending' || newStatus === 'in-progress' || newStatus === 'done';
const hasPermission = user === assignee || user === 'Marta';
let allowed;
let reason;
if (!hasPermission) {
allowed = false;
reason = `${user} cannot modify a task belonging to ${assignee}.`;
} else if (!IS_VALID_STATUS) {
allowed = false;
reason = `"${newStatus}" is not a status of the system.`;
} else if (currentStatus === newStatus) {
allowed = false;
reason = `The task is already in status "${currentStatus}".`;
} else if (currentStatus === 'pending' && newStatus === 'in-progress') {
allowed = true;
reason = 'Someone starts the task.';
} else if (currentStatus === 'in-progress' && newStatus === 'done') {
allowed = true;
reason = 'Task finished.';
} else if (currentStatus === 'in-progress' && newStatus === 'pending') {
allowed = true;
reason = 'The task is parked.';
} else if (currentStatus === 'done' && newStatus === 'in-progress') {
allowed = true;
reason = 'It is reopened for touch-ups.';
} else {
allowed = false;
reason = `Transition not allowed: ${currentStatus} → ${newStatus}.`;
}
console.log(allowed ? `✓ ${reason}` : `✗ ${reason}`);
// ✓ Task finished.With user = 'Lucía' and assignee = 'Iván', the first guard cuts things short immediately: ✗ Lucía cannot modify a task belonging to Iván. And notice that whether the transition was valid is not even evaluated, which is exactly what the exercise asked for.
hasPermission deserves its own variable because it expresses a business concept —authorization— that is different from the concept of a valid transition. When permission gets more complicated tomorrow (for example, also allowing an assigned reviewer), only that line changes.
The ternary in the console.log is well used: it picks a value —the ✓ or ✗ prefix— inside a template, which is exactly what it is for.
Conclusion
Your code now makes decisions. You know how to write an if with braces every time, add an else for the alternative path and chain else if to classify into more than two cases, taking advantage of the fact that each branch assumes everything the previous ones ruled out. You know how to combine conditions with &&, || and !, add parentheses for readability even when precedence is already correct, and —most important of all— give names to the pieces of a condition so that the final if reads like a sentence from the business.
You also know how to avoid two frequent traps: trusting truthy/falsy when 0, '' or an empty array are legitimate values, and nesting if inside if until you draw an arrow in the margin. The flat chain of guards is your tool against nesting, and the ternary is your tool for assigning one of two values, never for running actions and never for chaining four cases.
Applied to the project, you have already implemented two real pieces of Nómada Tasks: the urgency classification that combines priority and days left, and the status transition validation of rule R6, permissions included.
But notice something: all of that logic applies to a single task. Marta has a backlog of six, and next month she will have forty. Copying the urgency block forty times is not an option. That is solved in Loops: for, while, do-while, where you will learn to repeat a block of code as many times as necessary and to walk through all the workshop's tasks to add up their hours, find the most urgent one and count how many each person is carrying. With conditionals inside loops you will finally have a program that reasons about real data.
JavaScript Course: From Beginner to Advanced
Module 1: Introduction to JavaScript
- What Is JavaScript?
- Setting Up Your Development Environment
- Your First JavaScript Program
- JavaScript Syntax and Basic Concepts
- Variables and Data Types
- Basic Operators
- Type Conversion and Comparisons
- The Course Project: Nómada Tasks
Module 2: Control Structures
- Conditional Statements
- Loops: for, while, do-while
- Switch Statements
- Flow Control: break, continue and Nested Loops
- Error Handling with try-catch
Module 3: Functions
- Defining and Calling Functions
- Function Expressions and Arrow Functions
- Parameters and Return Values
- Scope and Closures
- Hoisting and the Execution Context
- Higher-Order Functions
- Recursion
Module 4: Objects and Arrays
- Introduction to Objects
- Object Methods and the
thisKeyword - Arrays: Basics and Methods
- Iterating over Arrays
- Searching, Sorting and Aggregating Data: find, sort and reduce
- Array Destructuring
- Object Destructuring, Spread and Rest
- JSON and Copying Objects
Module 5: Advanced Objects and Functions
- Prototypes and Inheritance
- Classes and Object-Oriented Programming
- Encapsulation: Getters, Setters and Private Fields
- Modules: Import and Export
- Asynchronous JavaScript: Callbacks
- Promises and Async/Await
- The Event Loop and the Microtask Queue
- Iterators and Generators
Module 6: The Document Object Model (DOM)
- Introduction to the DOM
- Selecting and Manipulating DOM Elements
- Handling Events
- Propagation, Delegation and Custom Events
- Creating and Removing DOM Elements
- Rendering Lists and HTML Templates
- Handling and Validating Forms
Module 7: Browser APIs and Advanced Topics
- Local and Session Storage
- The Fetch API and AJAX
- Robust Requests: Errors, Timeouts and AbortController
- WebSockets
- Service Workers and Progressive Web Apps (PWAs)
- Essential Browser APIs
- Introduction to WebAssembly
Module 8: Testing and Debugging
- Debugging JavaScript
- Code Quality: ESLint, Prettier and Conventions
- Unit Testing with Jest
- Test Doubles: Mocks, Stubs and Spies
- Integration Testing
- End-to-End Testing with Cypress
Module 9: Performance and Optimization
- Measure Before You Optimize: DevTools and Web Vitals
- Optimizing JavaScript Performance
- Memory Management
- Efficient DOM Manipulation
- Lazy Loading and Code Splitting
Module 10: JavaScript Frameworks and Libraries
- Why Frameworks Exist
- Introduction to React
- State Management with Redux
- Vue.js Basics
- Angular Basics
- Choosing the Right Framework
