In the previous lesson you got the program to make decisions. The problem is that it decided about a single task: to classify the urgency of the six tasks in Taller Nómada's backlog you would have to copy the same block six times, and when Marta adds the seventh you would have to touch the code again. A loop solves exactly that: you write the instructions once and tell the program how many times to repeat them. In this lesson you will meet JavaScript's three classic loops —for, while and do...while—, you will learn to walk through a list of tasks by index, and you will master the four patterns that solve 90% of real problems: counting, accumulating, finding the maximum and checking whether something exists.

Contents

  1. What a loop is and why you need one
  2. Anatomy of the for loop
  3. The while loop
  4. The do...while loop
  5. Which one to choose: a comparison table
  6. Walking a list by index with .length
  7. Taller Nómada's backlog
  8. The accumulator pattern: total backlog hours
  9. The counter pattern: how many tasks each person carries
  10. The maximum/minimum pattern: the most urgent task
  11. The flag pattern: is there any overdue task?
  12. Infinite loops and how to avoid them
  13. for...of and for...in: a preview
  14. Common Mistakes and Tips
  15. Exercises
  16. Conclusion

  1. What a loop is and why you need one

A loop (or iteration) is a control structure that runs a block of statements repeatedly while a condition holds. Each full pass through the block is called an iteration.

Without loops, the backlog summary is written like this:

// ✗ Unsustainable
console.log(`1. ${title1} · ${hours1} h`);
console.log(`2. ${title2} · ${hours2} h`);
console.log(`3. ${title3} · ${hours3} h`);
// ...and so on, up to forty

With a loop you write it once and it works with six tasks, with forty or with none. On top of that, if tomorrow you have to add the priority to the summary, you change one line instead of forty.

  1. Anatomy of the for loop

The for is the go-to loop when you know in advance how many times to repeat or when you are walking a list from start to finish. Its header contains three parts separated by ;:

for (initialization; condition; update) {
  // body
}

A minimal example: printing the first three task numbers.

for (let i = 1; i <= 3; i++) {
  console.log(`Processing task number ${i}`);
}

Output:

Processing task number 1
Processing task number 2
Processing task number 3

What each part does, and in what order:

Part When it runs In the example
Initialization Once only, before everything else let i = 1
Condition Before each iteration i <= 3
Body If the condition was true console.log(...)
Update After each iteration i++

The full flow, step by step:

flowchart TD
    A["Initialization<br/>let i = 1"] --> B{"Condition<br/>i <= 3"}
    B -->|false| F["End of the loop"]
    B -->|true| C["Body<br/>console.log(...)"]
    C --> D["Update<br/>i++"]
    D --> B

Follow the trace in your head:

Step i Condition i <= 3 What happens
1 1 true Prints "1", then i becomes 2
2 2 true Prints "2", then i becomes 3
3 3 true Prints "3", then i becomes 4
4 4 false The loop ends

Two fundamental observations:

  • The condition is checked BEFORE each iteration, including the first. If you start with let i = 5 and the condition is i <= 3, the body does not run even once. That is correct and desirable: a loop over an empty list must do zero iterations.
  • The update runs AFTER the body, not before. That is why i is still 1 during the first iteration.

2.1 The control variable

By convention it is called i (for index), and j, k if there are more. It is declared with let, never with const: it has to be able to change on every update.

// ✗ TypeError: Assignment to constant variable
for (const i = 0; i < 3; i++) { }

Declaring it in the header with let means it only exists inside the loop, which is exactly what you want:

for (let i = 0; i < 3; i++) {
  console.log(i);
}
console.log(i);   // ✗ ReferenceError: i is not defined

If you need to know the counter's final value after the loop, declare the variable outside. But you almost never need to: it is a sign that what you really wanted was a while.

2.2 Variations on the header

The three parts are optional, although the ; are not. These variants are legal:

let i = 0;
for (; i < 3; i++) { }        // no initialization

for (let j = 10; j > 0; j -= 2) { }   // counting down in steps of 2

for (let k = 0, total = 0; k < 3; k++) { }   // two variables, separated by a comma

Counting backwards is common when order matters:

for (let i = 5; i >= 1; i--) {
  console.log(`${i} tasks left to review`);
}

  1. The while loop

while repeats a block as long as a condition is true. It has no initialization or update of its own: that is up to you.

let pendingHours = 45;
let days = 0;
const HOURS_PER_DAY = 8;

while (pendingHours > 0) {
  pendingHours -= HOURS_PER_DAY;
  days++;
}

console.log(`The open backlog takes ${days} days of work.`);
// The open backlog takes 6 days of work.

It is exactly the same mechanism as a for with the three parts spread out:

let i = 0;                 // initialization: outside
while (i < 3) {            // condition: in the header
  console.log(i);
  i++;                     // update: last line of the body
}

So what is while for, if for does the same thing? For the cases where you do not know how many iterations will be needed. In the hours example we do not know in advance how many days it will come to: it depends on the total. Writing it with for would force you to calculate the number of iterations first, which is precisely what we wanted to find out.

  1. The do...while loop

do...while is identical to while with one decisive difference: the body runs at least once, because the condition is checked at the end.

let attempts = 0;
let taskFound = false;

do {
  attempts++;
  console.log(`Sync attempt no. ${attempts}`);
  taskFound = attempts === 3;   // we pretend the third attempt is the lucky one
} while (!taskFound && attempts < 5);

console.log(`Synced after ${attempts} attempt(s).`);

Notice the final ;: in do...while it is mandatory after the while, because the statement ends there. It is the only one of the three that has it.

The difference from while shows up with a condition that is false from the start:

let n = 10;

while (n < 5) {
  console.log('while: I never run');
}

do {
  console.log('do...while: I run once');
} while (n < 5);

Output:

do...while: I run once
flowchart LR
    subgraph W["while"]
      direction TB
      W1{"Condition?"} -->|true| W2["Body"]
      W2 --> W1
      W1 -->|false| W3["End"]
    end
    subgraph D["do...while"]
      direction TB
      D1["Body"] --> D2{"Condition?"}
      D2 -->|true| D1
      D2 -->|false| D3["End"]
    end

do...while is the least used of the three. Its niche is processes that always have to be attempted once before you decide whether to repeat them: asking for a value until it is valid, retrying a failed operation, generating an identifier until it is not a duplicate.

  1. Which one to choose: a comparison table

Criterion for while do...while
Number of iterations known ✓ Ideal △ Possible ✗ Rare
Number of iterations unknown △ Possible ✓ Ideal ✓ If it runs at least once
The body runs a minimum of 0 times 0 times 1 time
The condition is checked At the start At the start At the end
Counter visible in the header ✓ Yes ✗ No ✗ No
Walking a whole list ✓ The natural choice △ Verbose ✗ Fails with an empty list
Risk of an infinite loop Low Medium Medium
Needs a final ; No No Yes

Practical rule: if you are walking a list or repeating a known number of times, for. If you are repeating "until something happens", while. And if that something can only be evaluated after the first attempt, do...while.

  1. Walking a list by index with .length

To work with the backlog you need to store several values in a single variable. That is an array: an ordered list of values, written between square brackets and separated by commas.

const titles = [
  'Redesign the multipurpose room',
  'Signage for the screen-printing workshop',
  'Update the bookings website'
];

For now you only need three things from arrays; the rest arrives in Arrays: Basics and Methods:

What I need How it is written Example
How many elements there are array.length titles.length3
The element at position N array[N] titles[0]'Redesign the multipurpose room'
Walking through all of them A for with an index for (let i = 0; i < titles.length; i++)

Indexes start at 0. An array of 3 elements has indexes 0, 1 and 2. The last index is always length - 1, and array[length] gives undefined, not an error. This is the number-one cause of bugs with loops.

for (let i = 0; i < titles.length; i++) {
  console.log(`${i + 1}. ${titles[i]}`);
}

Output:

1. Redesign the multipurpose room
2. Signage for the screen-printing workshop
3. Update the bookings website

That header —let i = 0, i < array.length, i++— is the most repeated pattern in all of JavaScript. Make it second nature:

  • It starts at 0 because that is the first index.
  • It uses < and not <=, because length is the number of elements, not the last index.
  • The i + 1 in the message is purely cosmetic: it turns the index into a readable position number for Marta.

  1. Taller Nómada's backlog

You do not know how to create objects yet, so we will represent the six tasks with parallel arrays: one array per field, where position i in all of them describes the same task. This code will be the starting point for the following examples and for those in lessons 02-03 and 02-04.

const TODAY = '2026-09-20';

const ids        = [1, 2, 3, 4, 5, 6];
const titles     = [
  'Redesign the multipurpose room',
  'Signage for the screen-printing workshop',
  'Update the bookings website',
  'Screen-printing ink inventory',
  'Bookbinding guide for residents',
  'Carpentry workshop quote'
];
const assignees  = ['Iván', 'Marta', 'Lucía', 'Marta', 'Iván', 'Iván'];
const priorities = ['high', 'medium', 'high', 'low', 'medium', 'high'];
const statuses   = ['in-progress', 'pending', 'pending', 'done', 'in-progress', 'pending'];
const hours      = [12, 6, 14, 3, 8, 5];
const dueDates   = [
  '2026-09-30', '2026-10-15', '2026-10-02',
  '2026-09-12', '2026-11-05', '2026-09-05'
];

Reading task number 3 means looking at position 2 in every array: titles[2], assignees[2], hours[2]... Here is the full listing:

i id Title Assignee Priority Status Hours Due date
0 1 Redesign the multipurpose room Iván high in-progress 12 2026-09-30
1 2 Signage for the screen-printing workshop Marta medium pending 6 2026-10-15
2 3 Update the bookings website Lucía high pending 14 2026-10-02
3 4 Screen-printing ink inventory Marta low done 3 2026-09-12
4 5 Bookbinding guide for residents Iván medium in-progress 8 2026-11-05
5 6 Carpentry workshop quote Iván high pending 5 2026-09-05

Parallel arrays are a temporary and fragile solution. If you sort one and not the others, the data ends up scrambled with no warning. The correct representation is an array of objects, and it arrives in Module 4. In the meantime they work perfectly well for learning loops, and suffering their fragility will make you appreciate objects when they turn up.

A full listing of the backlog combining a loop and a conditional:

for (let i = 0; i < ids.length; i++) {
  const badge = statuses[i] === 'done' ? '✓' : '○';
  const alert = dueDates[i] < TODAY && statuses[i] !== 'done' ? ' ⚠ OVERDUE' : '';
  console.log(
    `${badge} [${ids[i]}] ${titles[i]} · ${assignees[i]} · ${priorities[i]} · ${hours[i]} h${alert}`
  );
}

Output:

○ [1] Redesign the multipurpose room · Iván · high · 12 h
○ [2] Signage for the screen-printing workshop · Marta · medium · 6 h
○ [3] Update the bookings website · Lucía · high · 14 h
✓ [4] Screen-printing ink inventory · Marta · low · 3 h
○ [5] Bookbinding guide for residents · Iván · medium · 8 h
○ [6] Carpentry workshop quote · Iván · high · 5 h ⚠ OVERDUE

There you have the module's two structures working together: the loop walks, the conditional decides. Task 4 is not flagged as overdue even though its date has passed, because it is done: rule R10 requires both conditions.

  1. The accumulator pattern: total backlog hours

The accumulator is a variable declared before the loop that keeps adding (or concatenating) on each iteration.

let totalHours = 0;
let openHours = 0;

for (let i = 0; i < hours.length; i++) {
  totalHours += hours[i];
  if (statuses[i] !== 'done') {
    openHours += hours[i];
  }
}

console.log(`Total estimated hours: ${totalHours} h`);   // 48 h
console.log(`Open work hours:       ${openHours} h`);    // 45 h

The three elements of the pattern:

  1. Initialize it outside the loop, with the neutral value: 0 for sums, 1 for products, '' for text. If you declare it inside, it resets on every iteration and the result is always the last value.
  2. Accumulate inside, with +=.
  3. Use it afterwards, once the loop has finished.

Declaring it with let is mandatory: const would prevent reassigning it.

An accumulator with a condition —like openHours— is the basis of all of Marta's reports: adding up only what meets a criterion. In Module 4 this will be written in one line with reduce, but the mechanism you learn here is exactly the same.

  1. The counter pattern: how many tasks each person carries

A counter is an accumulator that adds 1 instead of a value. With three people on the team, three counters and an else if chain are enough:

let ivanTasks = 0;
let martaTasks = 0;
let luciaTasks = 0;

for (let i = 0; i < assignees.length; i++) {
  if (assignees[i] === 'Iván') {
    ivanTasks++;
  } else if (assignees[i] === 'Marta') {
    martaTasks++;
  } else if (assignees[i] === 'Lucía') {
    luciaTasks++;
  } else {
    console.warn(`Unrecognized assignee on task ${ids[i]}: ${assignees[i]}`);
  }
}

console.log(`Iván:  ${ivanTasks} task(s)`);    // Iván:  3 task(s)
console.log(`Marta: ${martaTasks} task(s)`);   // Marta: 2 task(s)
console.log(`Lucía: ${luciaTasks} task(s)`);   // Lucía: 1 task(s)

The split confirms the diagnosis from lesson 01-08: Iván is overloaded and Lucía has room to spare. Exactly the kind of information Marta could not see with her wall of sticky notes.

This code has an obvious smell: three nearly identical variables and an else if chain that grows with every new person. If someone joins the team tomorrow, you have to touch four places. The clean solution is nested loops over an array of assignees (lesson 02-04) and, definitively, a counter object in Module 4. Recognizing the awkwardness now is part of the learning.

Combining a counter and an accumulator gives us Iván's real workload, which rule R7 needs (nobody exceeds 40 h):

let ivanHours = 0;

for (let i = 0; i < assignees.length; i++) {
  if (assignees[i] === 'Iván' && statuses[i] !== 'done') {
    ivanHours += hours[i];
  }
}

console.log(`Iván's open workload: ${ivanHours} h out of 40`);   // 25 h out of 40

if (ivanHours > 40) {
  console.error('R7 violated: Iván exceeds 40 hours per week.');
} else {
  console.log(`Room available: ${40 - ivanHours} h`);            // Room available: 15 h
}

  1. The maximum/minimum pattern: the most urgent task

To find the extreme value in a list you keep a provisional candidate and replace it every time a better one turns up. We are looking for the open task whose due date is the nearest:

let earliestDate = '';
let mostUrgentTitle = '';
let mostUrgentId = 0;

for (let i = 0; i < dueDates.length; i++) {
  const isCandidate = statuses[i] !== 'done';
  const isTheFirstOne = earliestDate === '';
  const isDueEarlier = dueDates[i] < earliestDate;

  if (isCandidate && (isTheFirstOne || isDueEarlier)) {
    earliestDate = dueDates[i];
    mostUrgentTitle = titles[i];
    mostUrgentId = ids[i];
  }
}

console.log(`Most urgent: [${mostUrgentId}] ${mostUrgentTitle} (due ${earliestDate})`);
// Most urgent: [6] Carpentry workshop quote (due 2026-09-05)

The three keys to the pattern:

  • The initial value must be impossible or must mark "no candidate yet". Here we use '', and the isTheFirstOne variable detects that case. The usual alternative —initializing with the first element— does not work here because the first element could be done and therefore discarded.
  • Comparing ISO dates as text works, as you have known since lesson 01-08.
  • You have to carry all of the winner's data across at once. Since the fields live in parallel arrays, when you find a better candidate you have to copy its date, its title and its id in the same block. Forgetting one produces a mixed-up result: one task's date with another task's title. That is the fragility of parallel arrays in action.

For the maximum the pattern is identical with the comparison flipped. The task with the most hours:

let maxHours = -1;
let longestTitle = '';

for (let i = 0; i < hours.length; i++) {
  if (hours[i] > maxHours) {
    maxHours = hours[i];
    longestTitle = titles[i];
  }
}

console.log(`The longest one: ${longestTitle} (${maxHours} h)`);
// The longest one: Update the bookings website (14 h)

-1 as the initial value is safe because rule R3 guarantees that no task has negative hours. Initializing to 0 would work too, but -1 makes it clear that it is a sentinel and not a piece of data.

  1. The flag pattern: is there any overdue task?

A flag is a boolean variable that starts at false and is set to true as soon as what you are looking for happens. It answers yes-or-no questions about the whole list.

let hasOverdue = false;
let overdueCount = 0;

for (let i = 0; i < dueDates.length; i++) {
  if (dueDates[i] < TODAY && statuses[i] !== 'done') {
    hasOverdue = true;
    overdueCount++;
  }
}

if (hasOverdue) {
  console.error(`Warning: ${overdueCount} overdue task(s) on the board.`);
} else {
  console.log('No overdue tasks. Everything on schedule.');
}
// Warning: 1 overdue task(s) on the board.

The opposite flag —checking that all of them satisfy something— is initialized the other way round, at true, and set to false as soon as a counterexample turns up:

let allAssigned = true;

for (let i = 0; i < assignees.length; i++) {
  if (assignees[i] === null) {
    allAssigned = false;
  }
}

console.log(allAssigned ? 'Every task has an assignee.' : 'There are unassigned tasks.');
Question Initial value It changes to When
Is there any that satisfies X? false true On finding the first one that satisfies it
Do all of them satisfy X? true false On finding the first one that does not

In both cases the loop keeps walking the list even though the answer is already known. That is wasted work, and in lesson 02-04 you will learn to cut it short with break.

  1. Infinite loops and how to avoid them

An infinite loop is one whose condition never becomes false. In the browser the tab freezes; in Node.js the process hangs until you kill it with Ctrl + C.

The three usual causes:

Forgetting the update.

// ✗ i stays 0 forever
let i = 0;
while (i < 6) {
  console.log(titles[i]);
}

Updating in the wrong direction.

// ✗ i grows, the condition asks it to come down
for (let i = 6; i > 0; i++) { }

Modifying the counter inside the body without realizing.

// ✗ i goes back to 0 at the first unassigned task and never gets out
for (let i = 0; i < assignees.length; i++) {
  if (assignees[i] === null) {
    i = 0;
  }
}

How to protect yourself:

  • Check before running that the condition can become false. Find which variable appears in the condition and verify that the body or the update moves it toward the end.
  • Do not modify the control variable inside the body. If you think you need to, you almost certainly wanted a while.
  • Add a safety limit in loops whose condition depends on external data:
let attempts = 0;
const MAX_ATTEMPTS = 1000;

while (!hasOverdue && attempts < MAX_ATTEMPTS) {
  attempts++;
  // ...
}

if (attempts === MAX_ATTEMPTS) {
  console.error('Safety limit reached: check the loop condition.');
}
  • If you have already frozen the tab, close it and use node file.js with Ctrl + C to test dubious loops: it is far easier to stop.

There is one legitimate exception: while (true) with an explicit exit inside. It is used in retry loops and it always carries a break, which you will see in the next lesson.

  1. for...of and for...in: a preview

Modern JavaScript has two more loops, designed to walk collections without managing the index by hand. We introduce them here so you can recognize them; the detail about arrays is in Iterating over Arrays.

for...of walks the values of an array (or of anything iterable, such as a string):

for (const title of titles) {
  console.log(title);
}

Cleaner than the classic for when you only need the value and not the position. Notice that the variable can be const, because it is created afresh on each iteration. The problem in our case is that with parallel arrays we do need the index in order to look up the other fields, so we will keep using the classic for throughout this module.

for...in walks the keys (in an array, the indexes, and moreover as text):

for (const i of titles) { }   // values
for (const i in titles) {     // indexes, but '0', '1', '2'... as strings
  console.log(i + 1);         // ✗ '01', '11', '21' — it concatenates, it does not add
}

That concatenation trap is the main reason for the universal rule: for...in is for objects, not for arrays. It also walks inherited properties and does not guarantee the order. When you get to objects in Module 4 you will find its proper place.

Loop What it gives you Use it for
for (let i = 0; ...) The numeric index Arrays when you need the position
for...of The value Arrays and strings when you only want the value
for...in The key as text Objects, not arrays

Common Mistakes and Tips

Using <= with .length.

for (let i = 0; i <= titles.length; i++) { }   // ✗ one iteration too many

On the last pass titles[6] is undefined, and the summary prints "undefined · undefined". With .length it is always <.

Starting at 1. for (let i = 1; i < titles.length; i++) silently skips the first task. Indexes start at 0.

Declaring the accumulator inside the loop.

for (let i = 0; i < hours.length; i++) {
  let total = 0;        // ✗ it resets on every pass
  total += hours[i];
}

The accumulator always ends up equal to the last element, and on top of that it disappears on the way out. It goes before the loop.

Confusing the index with the value. hours[i] is the number of hours; i is the position. totalHours += i adds up 0+1+2+3+4+5 = 15, a number that looks plausible and is not. When a total comes out strange, check this first.

Modifying .length inside the loop. Adding or removing elements while you walk changes the condition on the fly and produces skipped items or infinite loops. Walk in order to read; build the modified list separately.

Forgetting the ; in do...while. It is the only one that has it, and the ASI you saw in lesson 01-04 does not always save you.

Tip: name the control variable well. i is universally accepted for indexes, but if the loop is long, let taskIndex reads better three months later.

Tip: if the loop body goes past twenty lines, something is wrong. In Module 3 you will learn to extract that logic into functions; for now, at least pull the calculations out into named variables before the if.

Tip: print the trace when something does not add up. A console.log(i, titles[i], totalHours) as the first line of the body shows you the state on every pass and solves most bugs in seconds.

Exercises

Exercise 1 — Workshop summary

Starting from the backlog in section 7, write a single for loop that calculates and displays:

  • The total number of tasks.
  • How many are pending, how many in-progress and how many done.
  • The progress percentage, understood as done tasks over the total, rounded to one decimal place.

Remember from lesson 01-06 that Math.round(x * 10) / 10 rounds to one decimal place.

Exercise 2 — Due-date countdown

Write a while loop that, starting from openHours = 45 and an 8-hour working day, calculates how many working days are needed to empty the backlog, printing on each pass the hours still left. Then answer: why is while a better choice here than for?

Exercise 3 — Highest-weight task

Using the priority-to-weight conversion from the previous lesson (high = 3, medium = 2, low = 1), calculate for each open task an effort index equal to weight * hours, and find the open task with the highest index. Display the full listing with its index and, at the end, the winner.

Solutions

Exercise 1

let pending = 0;
let inProgress = 0;
let done = 0;

for (let i = 0; i < statuses.length; i++) {
  if (statuses[i] === 'pending') {
    pending++;
  } else if (statuses[i] === 'in-progress') {
    inProgress++;
  } else if (statuses[i] === 'done') {
    done++;
  } else {
    console.warn(`Unknown status on task ${ids[i]}: ${statuses[i]}`);
  }
}

const total = statuses.length;
const progress = Math.round((done / total) * 100 * 10) / 10;

console.log(`Total tasks: ${total}`);          // 6
console.log(`Pending:     ${pending}`);        // 3
console.log(`In progress: ${inProgress}`);     // 2
console.log(`Done:        ${done}`);           // 1
console.log(`Progress: ${progress} %`);        // 16.7 %

Three counters updated in the same pass: walking the list once and calculating everything you need in that single pass is always preferable to writing three loops. The final else protects against unforeseen statuses, applying the rule from lesson 02-01 about always closing your chains.

The percentage calculation deserves attention: done / total gives 0.1666...; multiplying by 100 gives 16.666...; and the trick of multiplying by 10, rounding and dividing by 10 leaves 16.7 as a number. toFixed(1) would give the text '16.7', and if you later added it to something else you would get a concatenation instead of a sum.

Exercise 2

let openHours = 45;
const HOURS_PER_DAY = 8;
let days = 0;

while (openHours > 0) {
  days++;
  openHours -= HOURS_PER_DAY;
  const remaining = openHours > 0 ? openHours : 0;
  console.log(`Day ${days}: ${remaining} h left`);
}

console.log(`The open backlog takes ${days} working day(s).`);

Output:

Day 1: 37 h left
Day 2: 29 h left
Day 3: 21 h left
Day 4: 13 h left
Day 5: 5 h left
Day 6: 0 h left
The open backlog takes 6 working day(s).

The ternary avoids showing -3 h on the last pass, because subtracting 8 from 5 goes past zero.

Why while: the number of iterations is not known before you start; it is precisely what the loop calculates. With a for you would have to write for (let d = 0; d < Math.ceil(45 / 8); d++), that is, solve the problem in the header and then pretend to solve it in the body. The rule holds: when the stopping condition depends on a state that evolves, use while.

Exercise 3

let maxEffort = -1;
let winnerTitle = '';

for (let i = 0; i < ids.length; i++) {
  if (statuses[i] === 'done') {
    console.log(`[${ids[i]}] ${titles[i]} — closed, does not score`);
  } else {
    let weight;
    if (priorities[i] === 'high') {
      weight = 3;
    } else if (priorities[i] === 'medium') {
      weight = 2;
    } else {
      weight = 1;
    }

    const effort = weight * hours[i];
    console.log(`[${ids[i]}] ${titles[i]} — effort ${effort}`);

    if (effort > maxEffort) {
      maxEffort = effort;
      winnerTitle = titles[i];
    }
  }
}

console.log(`\nHighest effort: ${winnerTitle} (${maxEffort})`);

Output:

[1] Redesign the multipurpose room — effort 36
[2] Signage for the screen-printing workshop — effort 12
[3] Update the bookings website — effort 42
[4] Screen-printing ink inventory — closed, does not score
[5] Bookbinding guide for residents — effort 16
[6] Carpentry workshop quote — effort 15

Highest effort: Update the bookings website (42)

Three details worth noticing. First, let weight; is declared inside the loop on purpose: it is a different value on each iteration, not an accumulator, so it must be reset. Second, maxEffort and winnerTitle do go outside, because they have to survive between passes. And third, this chain of three else if repeated on every iteration is exactly the case that the switch in the next lesson will solve more compactly.

Conclusion

You now know how to repeat. You know the for with its triple header —initialization, condition and update— and the exact order in which they run; the while, for when you do not know how many passes will be needed; and the do...while, for what has to be attempted at least once. You know how to walk a list with for (let i = 0; i < array.length; i++), which is the most-written header in all of JavaScript, and why indexes go from 0 to length - 1.

Above all, you have mastered the four patterns that solve almost any traversal: the accumulator for adding up hours, the counter for splitting tasks per person, the maximum/minimum for finding the most urgent one and the flag for answering whether any is overdue. All four share the same structure: a variable declared before the loop, updated inside and read afterwards. And you know how to spot and avoid an infinite loop before you freeze the tab.

Applied to Nómada Tasks, you can already generate the report Marta never had: 48 estimated hours in total, 45 of open work, 16.7% progress, one overdue task and a very uneven workload —Iván with three tasks and 25 hours against Lucía's single one—.

You will have noticed two awkward spots. One: converting priority into a weight forced you to repeat the same else if chain over and over. Two: the loops keep walking the whole list even when the answer is already known. The first is tackled in Switch Statements, where you will learn a construct designed exactly for choosing between several values of the same variable, and the second in the next lesson, with break and continue.

JavaScript Course: From Beginner to Advanced

Module 1: Introduction to JavaScript

Module 2: Control Structures

Module 3: Functions

Module 4: Objects and Arrays

Module 5: Advanced Objects and Functions

Module 6: The Document Object Model (DOM)

Module 7: Browser APIs and Advanced Topics

Module 8: Testing and Debugging

Module 9: Performance and Optimization

Module 10: JavaScript Frameworks and Libraries

Module 11: Final Project

© Copyright 2026. All rights reserved