Loops and iteration
Computers are good at doing the same thing many times without getting bored. JavaScript has five ways to ask for that, and one of them hands you string indices when you were expecting numbers.
for
The classic counted loop:
for (let i = 1; i <= 3; i++) {
console.log(`Day ${i}`);
}
Day 1
Day 2
Day 3
Three parts separated by semicolons: start, keep going while, and after
each pass. Use let, not var — module 2's first lesson showed why.
Off-by-one errors live here. i <= 3 runs three times starting from 1; i < 3
starting from 0 also runs three times, giving 0, 1 and 2. Since arrays are
numbered from zero, the second form is the one you will write most.
while
When you do not know in advance how many passes you need:
let balance = 500;
let days = 0;
while (balance >= 80) {
balance -= 80;
days++;
}
console.log(`${days} days of tiffin, ₹${balance} left over.`);
6 days of tiffin, ₹20 left over.
Something inside the loop must eventually make the condition false. Forget
that and you have an infinite loop: in Node, Ctrl + C; in a browser tab, the
page freezes and you close the tab. It will happen to you at least once.
do...while is the rare variant that always runs at least once, because it
checks at the bottom:
let n = 0;
let runs = 0;
do {
runs++;
} while (n > 0);
console.log(runs);
1
The condition was false from the start and the body still ran once. Use it when you must ask before you can test the answer.
for...of — the one to reach for
For going through the values of an array or string:
const orders = ['Priya', 'Arjun', 'Meera'];
for (const customer of orders) {
console.log(`Tiffin for ${customer}`);
}
Tiffin for Priya
Tiffin for Arjun
Tiffin for Meera
No counter, no off-by-one, no orders[i]. This is the default loop for going
through a collection. const is correct here — each pass gets its own fresh
binding, so nothing is being reassigned.
It works on strings too:
for (const character of 'dal') {
console.log(character);
}
d
a
l
When you need the position as well, use .entries():
for (const [index, customer] of orders.entries()) {
console.log(`${index + 1}. ${customer}`);
}
1. Priya
2. Arjun
3. Meera
The square brackets are destructuring, which is module 4. Take it on trust for now.
The trap: for...in is not for arrays
for...in looks like the same thing with a different preposition. It is not.
const orders = ['Priya', 'Arjun'];
for (const item of orders) {
console.log(item);
}
for (const item in orders) {
console.log(item);
}
Priya
Arjun
0
1
for...in gives you the keys, not the values. And those keys are strings,
not numbers — which is where it stops being a nuisance and becomes a bug:
for (const i in orders) {
console.log(i + 1);
}
01
11
You expected 1 and 2. You got '01' and '11', because i is the string
'0' and + joins text. Silent, plausible and wrong — exactly the coercion bug
from the types lesson, delivered by a loop.
for...in has a second problem: it also walks properties inherited from
elsewhere, so a library that adds something to every array can put extra
iterations into your loop.
Use for...of for arrays. Use for...in only for the keys of a plain object
— which module 4 covers, alongside better tools for that too.
One more thing for...of will not do: plain objects.
for (const x of { plates: 3 }) {
console.log(x);
}
TypeError: {(intermediate value)} is not iterable
Objects are not iterable. Object.keys(), Object.values() and
Object.entries() are the answer, in module 4.
Stopping early: break and continue
break leaves the loop; continue skips to the next pass.
const plates = [3, 5, 0, 4, 2];
for (const count of plates) {
if (count === 0) continue;
console.log(`${count} plates`);
}
3 plates
5 plates
4 plates
2 plates
let running = 0;
for (const count of plates) {
running += count;
if (running > 8) {
console.log(`Stopped at ${running}.`);
break;
}
}
Stopped at 12.
Both only affect the innermost loop they are inside. Breaking out of two nested loops needs a flag or — better — moving the inner loop into a function and returning from it. Functions are the next module.
Which loop to use
| Loop | Use it for |
|---|---|
for...of |
Default. Values of an array, string or Map. |
for (let i = ...) |
When you need the index, or a step other than 1, or to count backwards. |
while |
When the number of passes is not known up front. |
do...while |
When the body must run at least once. |
for...in |
Keys of a plain object. Never an array. |
Module 4 adds map, filter, reduce and forEach, which replace most of the
loops you would otherwise write for arrays. They are not a different kind of
loop so much as a way of saying what the loop is for. Learn these first —
every one of those methods is a loop underneath, and debugging them is much
easier when you can picture it.
Check your work
for (let i = 1; i <= 3; i++) runs three times, printing Day 1, 2 and 3.
With i = 0; i < 3 it also runs three times, printing 0, 1, 2 — the form you
want for array indices.
The ₹500 balance loop gives 6 days of tiffin, ₹20 left over. Six lots of
₹80 is ₹480, and ₹20 is not enough for a seventh.
do...while with a false condition still runs once, printing 1. It tests
at the bottom.
for...in over ['Priya', 'Arjun'] prints 0 and 1, not the names —
those are the keys. for...of prints the names.
i + 1 inside a for...in over an array gives '01' and '11'. The keys
are strings, and + joins text rather than adding. Use for...of, or
.entries() when you need a real numeric index.
for...of over a plain object throws TypeError: {(intermediate value)} is not iterable. Objects are not iterable; use Object.entries() in module 4.
continue on count === 0 skips the zero and prints the other four counts.
break once the running total passes 8 stops at 12, because 3 + 5 + 0 + 4
reaches 12 on the fourth pass — the check happens after adding, so it reports
the value that broke the limit rather than the one before it.
break and continue affect only the innermost loop.
₹1000 at ₹80 a day is 12 days with ₹40 left over.
The digits of '411014' add up to 11. If you forget Number() on each
character you get the string '0411014' instead — a running total that started
as 0 and had six characters joined onto it.
The running-total exercise, with [3, 5, 0, 4, 2] at ₹80:
const plates = [3, 5, 0, 4, 2];
let total = 0;
let delivered = 0;
for (const [i, count] of plates.entries()) {
if (count === 0) continue;
total += count * 80;
delivered++;
console.log(`Day ${i + 1}: ${count} plates, running ₹${total}`);
}
console.log(`Total ₹${total} over ${delivered} days`);
Day 1: 3 plates, running ₹240
Day 2: 5 plates, running ₹640
Day 4: 4 plates, running ₹960
Day 5: 2 plates, running ₹1120
Total ₹1120 over 4 days
Day 3 is missing from the output, which is the continue doing its job — but
note the day numbers stay correct, because they come from .entries() rather
than from a counter you increment yourself. A separate counter would have
renumbered day 4 as day 3.
Practice
- Print the numbers 1 to 10 with a
forloop. Then print them backwards. Then print only the even ones, two different ways — with%and with a step of 2. - Print
Day 1toDay 7and predict the loop bounds before running it. - Use a
whileloop to work out how many days ₹1000 lasts at ₹80 a day, and how much is left over. - Write an infinite loop on purpose in Node and stop it with
Ctrl + C. Knowing what it looks like — and that you are not stuck — is worth the thirty seconds. - Loop over
['Priya', 'Arjun', 'Meera']withfor...ofand print a line for each. - Do the
for...inexperiment. Loop over the same array withfor...in, print each key, then printkey + 1and explain the'01'to yourself out loud before fixing it. - Use
.entries()to print a numbered list starting at 1. - Loop over
'411014'character by character and add the digits up. You will needNumber()on each character — and if you forget, you will get'0411014', which is the coercion bug once more. - Use
continueto skip cancelled orders (a count of0) andbreakto stop once a running total passes 10. - Harder. Given
[3, 5, 0, 4, 2]as plates per day at ₹80, print a line per day with a running total, skip the cancelled days, and print the final total and the number of days actually delivered. Do it with one loop, not three.
That is module two. You can now hold values, know what those values actually
are, build text out of them, make decisions, and repeat work — and you have met
the three bugs that come from JavaScript's willingness to convert things behind
your back: '10' > '9', a truthy '0', and a falsy real zero.
Those are the bugs you will actually write. The next module is where the
language stops being a list of features and starts having a shape: functions,
scope, closures, and this. It is the conceptual core of JavaScript, and it is
the part that most people never quite nail down.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship