JavaScript: Loops

for

The standard for loop

for(){}
for (initializer; condition; final-expression) {
    code block
}
Consists of:
  • for key word
  • Parentheses (initializer; condition; final-expression)
    • Initializer or counter variable
    • Condition
    • Final-expression: Usually an increment/decrement that bring the counter variable closer to the condition no longer true. Run each time loop has gone through a full iteration.
  • Curly braces {code block}
    • Code block to run
Very common use:
for (let i = 0; i < upperLimit; i++) {
    code block
}
Loop body
The code block inside loop's {}
Iteration
A single execution of the loop body. A cycle.
Inline variable declaration
Declaration of a variable within for loop's braces.
  • for (let i = 0; ...)
  • Such variables are visible only inside the loop.
  • We can also use existing variable declared outside the loop: let i = 0; for (i = 0;...).
  • Such variables will be visible outside, off course.
  • For just a counter, this is not desirable. Limiting scope as tight as possible is better.
Any part of for can be skipped.
  • 2 semicolons for(;;) can't be omitted. Otherwise, syntax error.
  • Skipping initializer
    let i = 0;
    for (; i < 3; i++) {
        alert (i);
    }
    
  • Skipping final-expression
    let i = 0;
    for (; i < 3;) {
        alert (i++);
    }
    
    // compare to loop while(i < 3)
    
  • Now let's skip the condition. It can, it makes an infinite loop
    for (;;) {
    }
    

while

The while loop

while () {}
initializer;
while (condition) {
    ...codeblock...
    final-expression
}
Similar to for loop, but:
  • Only condition, such as i < array.length, is written between parentheses.
  • Initializer, such as let i = 0;, is written before the loop
  • Final expression that's usually increment or decrement, such as i++, is written at the end of the code block.
let i = 0;
while (i < array.length) {
    ...codeblock...
    i++
}
For example
const animals = ["dodo", "great auk", "thylacine"];

let favAnimals = "Harry's favourite extinct animals are ";

let i = 0;

while (i < animals.length) {
    if (i === animals.length - 1) {
        favAnimals += `and ${animals[i]}.`;
    } else {
        favAnimals += `${animals[i]}, `;
    }
    i++
}
Notes
  • Condition is a true/false comparison. For example, these are all the same:
    • let i = 3; while (i > 0) {...; i--}
    • let i = 3; while (i != 0) {...; i--}
    • let i = 3; while (i) {...; i--}
  • Curly braces not required when a loop body only has 1 single statement.
    • let i = 3; while (i) {alert(i--)};
    • let i = 3; while (i) alert(i--);

do...while

Loop that executes at least once

do {} while ()
do...while is similar to while with one main difference:
  • do---while:
    • The codeblock is run before the condition is checked.
    • The codeblock gets run at least once.
  • while and for:
    • The condition is read first before goes to the codeblock.
    • It's possible that the codeblock never get run because never get read. If the condition is not met right away.
  • Which one to use?
    • for and while are preferred.
    • Use do...while only when loop body must be executed at least once regardless the condition.
initializer
do {
    ...codeblock...
    final-expression
} while (condition)

Infinite loop

Loop that can't stop

Infinite loop
Examples:
while (true) {...}

for (let i = 1; i > 0; i++) {...}
Infinite loop can cause crash
Remember to increment or decrement loop in such that it will hit the point condition is not met anymore. Otherwise loop with go forever. This is called an infinite loop. The browser will either force it to stop, or it will crash.
Infinite loop can be useful:
Loop until a break happens or program terminated.

for...of

Special loop for collection

for(member of collection)
for (const item of collection) {}
Used to loop through a collection, for example an array.
const collection = ["dodo", "great auk", "woolly mammoth", "thylacine"];

for (const item of collection) {
    console.log(item);
}
Console:
dodo
great auk
woolly mammoth
thylacine

for vs for...of

Which one to use for collection?

for...of
for (member of collection) {
    ...member...
}
When index doesn't make difference, use for...of for simplicity and readability. Simplicity reduces chance of errors.
Common errors in standard for loop for collection:
  • Error introduced by forgetting index starts at 0 and ends at length-1.
    • Initializing: i = 1 instead of i = 0
    • Condition: i <= collection.length instead of i < collection.length
    • Which is individually fine as long as addressing the member in the code block is adjusted too, for example by using collection[i-1] instead of collection[i].
    • But this is getting even more confusing. Especially when working with other people, it's important for our code to be as clear as possible.
  • Components inside parentheses is semicolon-separated (;), instead of comma.
for
for (let i = 0; i < collection.length; i++) {
    ...collection[i]...
}
When index matter, using the standard for loop is more practical. Just use if condition to the index.
  • Example: When displaying comma-space separated list, usually the last item should not have the trailing comma. Even better, it should be preceeded with an and.
const names = ["Adam", "Ben", "Chris"];

let students = "Students today are ";

for (const name of names) {
    students += `${name}, `;
}

students;
// 'Students today are Adam, Ben, Chris, '
Using standard for loop
const names = ["Adam", "Ben", "Chris"];

let students = "Students today are ";

for (let i = 0; i < names.length; i++) {
    if (i < (names.length - 1)) {
        students += `${names[i]}, `;
    } else if (i === (names.length - 1)){
        students += `and ${names[i]}.`;
    }   
}

students;
// 'Students today are Adam, Ben, and Chris.'

map

Loop that maps of a collection

Full notes: CodeyLuwak: Array.map()

.map(function)
const new = arr.map(function)
To do something to each item in a collection and create a new collection containing the changed items. In other word, mapping.
function makeUpper(text) {
    return text.toUpperCase();
}

const animals = ["dodo", "great auk", "woolly mammoth", "thylacine"];
const animalsUpper = animals.map(makeUpper);

animalsUpper;       // (4) ['DODO', 'GREAT AUK', 'WOOLLY MAMMOTH', 'THYLACINE']

filter

Loop that filters a collection

Full notes: CodeyLuwak: Array.filter()

.filter(function)
const new = arr.filter(function)
To create a new collection from items of an existing collection that fulfil some criteria.
Function here needs to have boolean return. If the return is true, then the item is included in the new array.
function lCat(cat) {
    return cat.startsWith("L");
}

const cats = ["Leopard", "Tiger", "Jaguar", "Lion"];
const filtered = cats.filter(lCat);

filtered;       // (2) ['Leopard', 'Lion']

break

Exit from the loop prematurely

break
  • In switch statement: break statement immediately exits the switch statement and moves on to the code after it.
  • In loops: break statement immediately exit the loop and move on to any code after it.
let pets = ["dog", "cat", "goldfish", "tarantula", "rabbit", "mouse"];
let search = "tarantula";
for (pet of pets) {
    if (search === pet) {
        alert (`It's a ${pet}!`);
        break;
    }
    alert (`It's not a ${pet}...`);
}
Result in alert box:
It's not a dog...
It's not a cat...
It's not a goldfish...
It's a tarantula!

(rabbit and mouse are skipped)
Try:

Pet choice: dog, cat, goldfish, tarantula, rabbit, mouse.

HTML codes
<p>Pet choice: dog, cat, goldfish, tarantula, rabbit, mouse.</p>
<label for="petInput">Choose a pet:</label>
<input id="petInput" type="text">
<button id="petButton">Go!</button>
<div id="petOutput"></div>
JS codes (choose_pet.js)
function choosePet () {
    const petInput = document.getElementById("petInput");
    let search = petInput.value;
    search = search.trim();
    search = search.toLowerCase();
    const petOutput = document.getElementById("petOutput");
    petOutput.textContent = "";

    let pets = ["dog", "cat", "goldfish", "tarantula", "rabbit", "mouse"];
    for (pet of pets) {
        if (search === pet) {
            const newPara = document.createElement("p");
            newPara.textContent = `It's a ${pet}!`;
            petOutput.appendChild(newPara);
            break;
        }
        const newPara = document.createElement("p");
        newPara.textContent = `It's not a ${pet}...`;
        petOutput.appendChild(newPara);
    }
}

const petButton = document.getElementById("petButton");
petButton.addEventListener("click", choosePet)
Infinite loop + break combo: break as exit condition
Create a simple sum function:
  • Function takes number inputed by user
  • User inputed number once at a time
  • Function sums up those numbers
  • while loop
  • While condition is set to true. Meaning it will run forever unless something else stop it.
  • break statement is used to break out the while loop. It is set to break when no number is entered.
HTML codes
<button id="sumButton">Sum Numbers</button>
JS codes (sumNumbers.js)
function sumNumbers () {
    let sum = 0;
    let statement = "";
    while (true) {
        const value = +prompt("Enter a non-zero number:");
        if (!value) break;
        sum += value;
        if (statement) statement += " + ";
        statement += value;
    }
    if (statement) alert (statement + " = " + sum);
}

const sumButton = document.getElementById("sumButton");
sumButton.addEventListener("click", sumNumbers);
Example: search function
Usually quits with result upon first match. When there's no match, at the end of the run, ends with "not found" comment.
Pseudocode:
  • Const list array
  • User input: searched value
  • Search function
    • Run loop through the list
      • Match?
        • Output result
        • Break
    • "Not found" (This part can only be reached when loop is done running through all members but no match is found.

HTML codes
<label for = "searchInput">Search by contact name:</label>
<input id="searchInput" type="text">
<button id="searchButton">Search</button>
<p id="searchResult"></p>    
JS codes (search_contact.js)
const contacts = ["Adam:121212", "Ben:343434", "Chris:565656", "Daniel:787878", "Ebert:909090"];
const searchInput = document.getElementById("searchInput");
const searchButton = document.getElementById("searchButton");
const searchResult = document.getElementById("searchResult");

searchButton.addEventListener("click", () => {
    const searchName = searchInput.value.toLowerCase();
    searchInput.value = "";
    searchInput.focus();
    if (searchName === "") {
        searchResult.textContent = "Please enter a name.";
    } else {
        for (const contact of contacts) {
            const splitContact = contact.split(":");
            if (splitContact[0].toLowerCase() === searchName) {
                searchResult.textContent = `${splitContact[0]}\'s number is ${splitContact[1]}.`;
                break;
            }
            searchResult.textContent = "Contact not found.";
        }
    }    
});

continue

Jump to the next iteration prematurely

continue
Similar to break, but instead of leaving the loop entirely, it stops the current iteration, skips directly to the next iteration of the loop.
In this example, each loop includes an alert saying it's the pet. However when it's not actually the pet, the iteration is truncated using continue directive.
let pets = ["dog", "cat", "goldfish", "tarantula", "rabbit", "mouse"];
let search = "tarantula";
for (pet of pets) {
    if (search !== pet) continue;
    alert (`It's a ${pet}!`);
}

// It's a tarantula!
Print only odd number. When the number is even, the iteration got truncated by continue directive.
let odd = "Odd numbers: "
for (let i = 0; i < 10; i++) {
    if (i % 2 === 0) continue;
    odd += i + " ";
}
odd;    // 'Odd numbers: 1 3 5 7 9 '
Example: Square root number game
Rule:
  • Input: Positive integer.
  • Output: integer square between 1 to the input number.
Pseudocode:
  • Get input number
  • Run loop from 1 to input number
    • if floor sqrt number is not sqrt number
      • continue to next iteration
    • print number
Square game - with continue

HTML codes
<label for="squareInput">Max:</label>
<input id="squareInput" type="text">
<button id="squareButton">Find squares</button>
<p id="squareResult"></p> 
JS codes (square_continue.js)
const squareInput = document.getElementById("squareInput");
const squareButton = document.getElementById("squareButton");
const squareResult = document.getElementById("squareResult");

squareButton.addEventListener("click", () => {
    let max = squareInput.value;
    squareInput.value = "";
    squareInput.focus();
    squareResult.textContent = `Squares up to ${max}: `;
    if ((max === "") || (isNaN(max) === true)) {
        squareResult.textContent = "Please enter a number.";
    } else {
        for (let i = 1; i <= max; i++) {
            let sqrtNum = Math.sqrt(i);
            if (Math.floor(sqrtNum) !== sqrtNum) {
                continue;
            }
            squareResult.textContent += `${i} `;
        }
    }
});
Alternative: without continue.
I get that we need to exercise continue keyword. However I think it's faster to do it without.
Pseudocode:
  • Get input number
  • Floor sqrt input number
  • Run loop for (let i = 1; i <= max; i++)
    • Print square of i
Square game - without continue

HTML codes
<label for="squareInput2">Max:</label>
<input id="squareInput2" type="text">
<button id="squareButton2">Find squares</button>
<p id="squareResult2"></p> 
JS codes (square_without_continue.js)
const squareInput2 = document.getElementById("squareInput2");
const squareButton2 = document.getElementById("squareButton2");
const squareResult2 = document.getElementById("squareResult2");

squareButton2.addEventListener("click", () => {
    let max = squareInput2.value;
    squareInput2.value = "";
    squareInput2.focus();
    squareResult2.textContent = `Squares up to ${max}: `;

    if ((max === "") || (isNaN(max) === true)) {
        squareResult2.textContent = "Please enter a number.";
    } else {
        let maxRoot = Math.floor(Math.sqrt(max));

        for (let i = 1; i <= maxRoot; i++) {
            squareResult2.textContent += `${i*i} `;
        }
    }   
});

Label for break and continue

For nested loop

Label for break
A label is an identifier with a colon ":" before a loop
break is used to get out of the immediate loop. If the loop is nesting, we can get out to where label is placed.
labelName: for (...) {
    for (...) {
        if (...) break labelName;
        codeblock
    }
}
Label can't just be placed anywhere relative to the break. Break has to be nested inside the codeblock labeled by it. The codeblock is usually a loop, even though it could be else.
Label for continue
Very similar to break, just for the function of continue. It jumps execution to the next iteration of the labeled loop.
In continue, the codeblock bust be a loop because continue is specific to loop.

In ternary?

Can't use break or continue

Don't use break or continue in ternary.
Note: directives such as break and continue can't be used with ternary operator ...? ... : ... because they are not expressions.
if (i > 5) {
    alert (i);
} else {
    continue;
}

// Can't be written as:
(i > 5) ? alert (i) : continue;      // syntax error

Loop exercises

Codey Luwak's notes: Loop Exercises


References