Repetition is what makes a computer worth having. Adding up a hundred numbers by hand is a chore; doing it once and asking the machine to repeat it a hundred times is a program. A loop is the construct that says do this block again, until some condition is false.
JavaScript gives you three shapes that cover almost everything, and the trick is picking the right one. A classic for counts with an index you control. for...of hands you each value of an array, no index needed. for...in hands you each key of an object. Newcomers mix up the last two constantly — for...of for an object throws, and for...in for an array hands back string indexes instead of values — so learning which is which is most of this lesson.
flowchart LR OF["for...of"] --> V["array values"] IN["for...in"] --> K["object keys"] style OF fill:#e0900b,color:#fff style IN fill:#e0900b,color:#fff
The classic for, part by part
The indexed for has three pieces inside the parentheses, separated by semicolons — initialise, test, update — and it repeats the body while the test stays true:
for (let i = 0; i < nums.length; i++) {
console.log(nums[i]);
}
let i = 0 runs once, before the first trip. i < nums.length is checked before every trip, including the first. i++ runs after each trip. Read those three slots in that order and the loop's behaviour is fully predictable.
The famous trap lives in the test. Arrays are zero-indexed and run from 0 to length - 1, so the correct bound is strictly less than (i < nums.length). Write i <= nums.length by mistake and the final trip reads nums[length] — one element past the end, which is undefined. Add that undefined into a sum and the whole total silently becomes NaN.
flowchart TD
I["let i = 0"] --> C{"i < arr.length?"}
C -- yes --> B["body: use arr[i]"]
B --> U["i++"]
U --> C
C -- no --> D["done"]
style C fill:#3776ab,color:#fff
style B fill:#e0900b,color:#fff
const nums = [10, 20, 30];
let sum = 0;
for (const n of nums) sum += n;
console.log(sum); // 60
const person = { name: "Ada", role: "dev" };
for (const key in person) console.log(key); // name, then role
for...of and for...in are not interchangeable
Use for...of for anything iterable — arrays and strings are the common ones — because it gives you each value directly, with no index to manage. Use for...in for the keys of a plain object, when you want the property names rather than their values.
Swapping them goes wrong in two characteristic ways. for...of on a plain object throws immediately, because plain objects are not iterable. for...in on an array does not throw — which is worse — it hands back the index as a string ("0", "1", ...) and can drag in keys inherited from the prototype chain. That is why the rule below is unconditional: never reach for for...in when you mean an array's values.
flowchart LR ARR["array [a, b, c]"] --> IN["for...in"] IN --> K["yields '0','1','2' (string indexes)"] IN --> J["+ any inherited prototype keys"] K --> RISK["not the values!"] style RISK fill:#b91c1c,color:#fff style K fill:#b45309,color:#fff
const nums = [3, 7, 2, 8, 1];
let firstBig = -1;
for (let i = 0; i < nums.length; i++) {
if (nums[i] > 5) { firstBig = nums[i]; break; } // stop at the first match
}
console.log(firstBig); // 7
let sum = 0;
for (const n of [1, 2, 3, 4]) {
if (n === 2) continue; // skip the 2
sum += n;
}
console.log(sum); // 8 (1 + 3 + 4)
Stepping out early, or choosing while
Two keywords steer a loop from inside its body. break exits the loop entirely the moment it runs — ideal when you are searching and have found your answer, because there is no point checking the rest. continue skips the remaining statements in the current trip and jumps straight to the next one — useful for skipping elements that do not interest you without nesting an else.
When you do not know how many trips you need, reach for while. It keeps repeating its body as long as the condition stays true, with no built-in counter, so the body must change something the condition reads — otherwise you have written an infinite loop. while is the natural fit for keep going until a condition is met; the indexed for is the natural fit for do this exactly N times.
let n = 1;
const doubles = [];
while (n < 20) {
doubles.push(n);
n = n * 2; // advance n, or the loop never ends
}
console.log(doubles); // [1,2,4,8,16]
What does this print? for...of gives each value of the array in order.
const fruits = ["apple", "banana"]; for (const f of fruits) console.log(f);
for...of yields each element value.
Each value is logged on its own line.
Write sumValues(obj) that adds up all the numeric values in an object using for...in, and returns the total.
function sumValues(obj) {
// return the sum of every value in obj
}
Start a total at 0 and loop with for...in.
Add obj[key] to the total for each key.
function sumValues(obj) {
let total = 0;
for (const key in obj) {
total += obj[key];
}
return total;
}
This should sum the numbers 1 through 5 (which is 15). But the loop test uses <=, so on the final trip it reads nums[5] — one position past the last real element — which is undefined. Adding undefined turns the running total into NaN. Fix the bound so the loop stops after the last real element.
function sumArray(nums) {
let sum = 0;
for (let i = 0; i <= nums.length; i++) {
sum += nums[i];
}
return sum;
}
Arrays run from index 0 to length - 1.
Use i < nums.length so the loop never reads the nonexistent element at nums.length.
function sumArray(nums) {
let sum = 0;
for (let i = 0; i < nums.length; i++) {
sum += nums[i];
}
return sum;
}
What does this print? for...in yields the KEYS of an object, not its values. They are collected into an array and logged once.
const user = { name: "Ada", role: "dev" };
const keys = [];
for (const k in user) keys.push(k);
console.log(keys);
for...in gives property names (keys).
So keys collects "name" and "role", in insertion order.
Write countVowels(str) that returns the number of vowel letters (a, e, i, o, u) in str, case-insensitive. Use any loop you like. This is the transfer task: the lesson examples show you how to iterate, but you must combine iteration with a condition and a running tally — nothing here is a snippet you can copy.
function countVowels(str) {
// return the count of a/e/i/o/u (case-insensitive)
}
Lowercase the string once with str.toLowerCase(), then loop its characters.
For each character, check if "aeiou".includes(ch) and add to a count.
function countVowels(str) {
const vowels = "aeiou";
let count = 0;
for (const ch of str.toLowerCase()) {
if (vowels.includes(ch)) count += 1;
}
return count;
}
Recap
- A classic
forhas three slots — initialise, test, update — and repeats while the test is true. Usei < arr.length, never<=, to avoid reading one past the end. for...ofwalks an array's (or string's) values;for...inwalks an object's keys. They are not interchangeable.for...ofon a plain object throws;for...inon an array gives string indexes and prototype junk — so never usefor...infor array values.breakexits a loop early;continueskips to the next trip.whilerepeats an unknown number of times — make sure the body advances the condition, or the loop never ends.- For transforming or filtering arrays, the array methods
mapandfilterare often clearer than a hand-written loop.