Module 2 · Data Types & Control Flow Deep Dive ⏱ 18 min

Loop Patterns & Iteration

By the end of this lesson you will be able to:
  • Read and write a classic indexed for loop without an off-by-one error
  • Use for...of to walk the values of an array and for...in to walk the keys of an object
  • Control a loop with break and continue, and choose while for an unknown count
  • Explain why for...in is the wrong tool for arrays

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
for...of yields array values; for...in yields object keys.

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
The classic for loop: initialise once, then test, body, update, repeat until the test is false.
for...of sums values; for...in walks an object's keys. Press Run.
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
for...in on an array yields string indexes and inherited junk — never the values.
break stops early; continue skips one trip. Use either loop shape you like.
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.

while repeats until its condition turns false — the body must advance it.
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]
Exercise

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);
Exercise

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
}
Exercise

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;
}
Exercise

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);
Exercise

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)
}

Recap

  • A classic for has three slots — initialise, test, update — and repeats while the test is true. Use i < arr.length, never <=, to avoid reading one past the end.
  • for...of walks an array's (or string's) values; for...in walks an object's keys. They are not interchangeable.
  • for...of on a plain object throws; for...in on an array gives string indexes and prototype junk — so never use for...in for array values.
  • break exits a loop early; continue skips to the next trip.
  • while repeats 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 map and filter are often clearer than a hand-written loop.

Checkpoint quiz

Which loop gives you the VALUES of an array, one at a time?

Why does for (let i = 0; i <= arr.length; i++) read past the end of the array?

Which loop gives you the KEYS of a plain object?

Go deeper — technical resources