Loops and Iteration

Table of Contents

  1. Why Repeat Things?
  2. Your First Array
  3. The Classic for Loop
  4. for...of: Loop Over Values
  5. while and do/while
  6. break and continue
  7. The for...in Caveat
  8. Strings and Nested Loops
  9. Avoiding Infinite Loops
  10. Try It Yourself

1. Why Repeat Things?

Dara's profile page will show four projects. Writing the same line of code four times would work, but it breaks as soon as a fifth project arrives. Computers are good at repeating work, and a loop is how you tell JavaScript to do that: run a block of code again and again until a condition says stop.

In the last chapter you used if to choose between two paths. A loop adds a second idea: go back and run the same path again. Almost every program uses both.

2. Your First Array

Loops need something to loop over. The most common thing is an array: an ordered list of values inside square brackets. Create a file named loops.js in your dara-profile folder:

const projects = [
  "Recipe Finder",
  "Weather Dashboard",
  "Khmer Typing Game",
  "Budget Tracker",
];

console.log(projects.length);
console.log(projects[0]);
console.log(projects[projects.length - 1]);

Run it with node loops.js. You will see 4, Recipe Finder, and Budget Tracker.

Three facts about arrays to remember:

  • Each value has a position called an index, and indexes start at 0, not 1. The first project is projects[0].
  • projects.length is the number of items, so the last index is always length - 1.
  • A trailing comma after the last item is allowed. It makes adding new lines easier.

Chapter 6 covers arrays fully. For now, treat the array as a list that the loops below will walk through. Later, this same data will fill the project cards on Dara's page.

3. The Classic for Loop

The for loop has three parts inside its parentheses:

for (let i = 0; i < projects.length; i++) {
  console.log(i + 1, projects[i]);
}
for ( let i = 0 ;  i < projects.length ;  i++ ) { ... }
      └───┬───┘    └───────┬────────┘    └─┬─┘
      start          keep going if       after each
                     this is true         round
PartExampleWhen it runs
Initializationlet i = 0Once, before the loop starts
Conditioni < projects.lengthBefore every round; the loop stops when it is false
Updatei++After every round (i++ means "add 1 to i")

The output is:

1 Recipe Finder
2 Weather Dashboard
3 Khmer Typing Game
4 Budget Tracker

The variable i is declared with let, not const, because its value changes. By convention it is called i (for "index"), and each round it takes the values 0, 1, 2, 3. When i becomes 4, the condition 4 < 4 is false, so the loop ends. Using < and not <= matters: projects[4] does not exist, and you would get undefined.

The classic for loop is the right choice when you need the index, or when you need to count in a special way, such as backwards or by twos.

4. for...of: Loop Over Values

Most of the time you do not care about the index. You just want each item. For that, use for...of:

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

It reads almost like English: "for each title of projects." There is no counter to start, test, or update, so there is less room for mistakes. Here const is fine, because a new title is created in each round.

If you need the index as well, ask the array for pairs with entries() and unpack them:

for (const [index, title] of projects.entries()) {
  console.log(`${index + 1}. ${title}`);
}

A good rule: start with for...of. Switch to the classic for only when you need full control of the counter. (Chapter 6 shows methods like map and filter that replace many loops, but they are built on the same idea.)

A loop is also how you add things up. A running total starts at zero and grows in each round:

let total = 0;

for (const n of [3, 5, 8]) {
  total += n;
}

console.log(total);

This prints 16. The += operator means "add to the existing value".

5. while and do/while

A while loop repeats as long as its condition is true. It is useful when you do not know in advance how many rounds you need.

let countdown = 3;

while (countdown > 0) {
  console.log(countdown);
  countdown--;
}

console.log("Go!");

This prints 3, 2, 1, then Go!. The condition is checked before each round, so if it starts as false the body never runs.

A do...while loop checks after the round, so the body always runs at least once:

let attempts = 0;
let number;

do {
  number = attempts * 3 + 1;
  attempts++;
} while (number < 8);

console.log(attempts, number);

This prints 4 10. You will rarely need do...while; it fits cases like "ask the user for input, then ask again if it was invalid." Know that it exists, and use for or while for everything else.

LoopUse it when
for...ofYou want each item of a list (the default choice)
forYou need the index or a custom counter
whileYou repeat until something changes, with an unknown count
do...whileThe body must run at least once

6. break and continue

Two keywords change how a loop runs from the inside:

  • continue skips the rest of this round and moves to the next one.
  • break stops the whole loop immediately.
for (const title of projects) {
  if (title.startsWith("W")) {
    continue; // skip "Weather Dashboard"
  }

  if (title === "Budget Tracker") {
    break; // stop here, nothing after this is visited
  }

  console.log(title);
}

The output is Recipe Finder and Khmer Typing Game. The weather project was skipped, and the loop ended when it reached the last one.

break is useful for searching: once you find what you want, there is no reason to keep looking.

let found;

for (const title of projects) {
  if (title.includes("Khmer")) {
    found = title;
    break;
  }
}

console.log(found); // "Khmer Typing Game"

(Chapter 6 shows find, which does this in one line.) Use break and continue with care. If a loop has many of them, it becomes hard to follow, and a clearer if is usually better.

7. The for...in Caveat

JavaScript has a third loop that looks similar: for...in. It does not give you values. It gives you the keys (names) of an object:

const levels = { html: 1, css: 2 };

for (const key in levels) {
  console.log(key, levels[key]);
}

This prints html 1 and css 2, and for objects it is acceptable. The problem is arrays. for...in on an array gives you the indexes as strings, and it also includes any extra properties someone added to the array:

const letters = ["x", "y"];
letters.extra = "oops";

for (const key in letters) {
  console.log(typeof key, key);
}

The output is string 0, string 1, and string extra. That is almost never what you want.

Rule: use for...of for arrays and strings. Use for...in only for plain objects, and prefer Object.entries() (chapter 6) even there.

8. Strings and Nested Loops

A string is also something you can loop over. for...of visits each character:

for (const char of "Dara") {
  console.log(char);
}

This prints D, a, r, a. Here is a small use, counting vowels:

let vowels = 0;

for (const char of "Mengsreang") {
  if ("aeiou".includes(char)) {
    vowels++;
  }
}

console.log(vowels); // 3

You can also put a loop inside another loop. The inner loop runs completely for every single round of the outer loop:

const skills = ["HTML", "CSS"];
const levels = ["basic", "good"];

for (const skill of skills) {
  for (const level of levels) {
    console.log(`${skill}-${level}`);
  }
}

The output is HTML-basic, HTML-good, CSS-basic, CSS-good: two outer rounds times two inner rounds is four lines. Nested loops multiply work. A loop of 1,000 inside a loop of 1,000 is a million rounds, so keep the nesting shallow.

One more pattern you will use often is counting with an object:

const tags = ["css", "js", "css"];
const counts = {};

for (const tag of tags) {
  counts[tag] = (counts[tag] ?? 0) + 1;
}

console.log(counts); // { css: 2, js: 1 }

The ?? 0 means "use 0 if there is no count yet". We use this exact idea when Dara's page shows how many projects have each tag.

9. Avoiding Infinite Loops

If a loop's condition never becomes false, it runs forever. This is called an infinite loop. In Node the terminal freezes; in a browser the tab stops responding.

// DON'T run this — the condition is always true
let n = 1;
while (n > 0) {
  n++;
}

The usual causes:

  • You forgot the update: no countdown--, so the number never changes.
  • The update goes the wrong way: counting i-- when the condition is i < 10.
  • The condition can never be false: while (true) with no break.

A short checklist before you run any loop:

  1. What changes in each round?
  2. Does that change move the loop toward the stopping condition?
  3. Will the condition really become false?

If you do get stuck, press Ctrl+C in the terminal to stop Node. In a browser, close the tab. Nothing is broken permanently; you only need to fix the loop and try again.

10. Try It Yourself

  1. Count backwards. Use a classic for loop to print the numbers from 10 down to 1, then Lift off!. Which part of the for do you have to change?
  2. Number the projects. Add a fifth project to the projects array and print the list as 1. Recipe Finder, 2. .... Did the loop need any change?
  3. Skip and stop. Use continue to skip every project with a title longer than 15 characters, and break to stop after printing two titles. (title.length gives the length.)
  4. Sum the evens. Loop through the numbers 1 to 20 and add up only the even ones. (Hint: n % 2 === 0.)
  5. Count letters. Loop over your own name and count how many times the letter a appears. Then make it work for both a and A. (Hint: char.toLowerCase().)
  6. Make it freeze, safely. Remove the countdown-- line from the while example and run it. Stop it with Ctrl+C. Then restore the line and explain in your own words why it froze.

Next, we'll package code like this into reusable functions and see how scope decides which variables each part of your program can see.

Buy Me a Coffee

Found this tutorial helpful? Consider buying me a coffee to support more free content!