What you'll be able to do
By the end of this page you should be able to repeat a block of code a set number of times, keep looping until a condition becomes false, walk through every item in a list with for...of, and walk through every key on a plain object with for...in — without using the wrong loop for the job.
Previous post: if, else, and switch.
Who this is for
- People who copied
for (let i = 0; i < list.length; i++)and did not know what each part meant - Beginners who used
for...inon an array and got indexes as strings (or weird extra keys) - Anyone who needs “do this for every name in the list” and is not sure which loop to open
You can skip this if you already pick for...of for array values, for...in only for object keys, and you can write a while that stops on purpose. Come back when a loop never ends, or when object keys show up while you meant list items.
Why loops exist
A loop means: run this block more than once. Without it, you would paste the same three lines ten times and miss a change on the eighth copy.
Scenario A — a list of names. You want to print each name, or build one HTML list item per name. The list can be 3 items today and 30 tomorrow. A loop scales; copy-paste does not.
Scenario B — wait until ready. You keep checking a flag or a counter until something is true. That is often a while — “keep going as long as this is still true.”
The rule: if the number of repeats depends on data (list length, user input, a condition), use a loop. If it is one decision, go back to if/else.
for: count with an index
The classic for loop has three parts in the parentheses: start, condition, step.
for (let i = 0; i < 3; i++) {
console.log("tick", i);
}
That prints tick 0, tick 1, tick 2. i starts at 0. The body runs while i < 3. After each run, i++ adds one.
Scenario A — walk an array by index. You need the index and the value:
const names = ["Asha", "Ravi", "Mia"];
for (let i = 0; i < names.length; i++) {
console.log(i, names[i]);
}
Scenario B — count down or skip. You can change the step: i += 2 for every other item, or i-- to go backwards. Just keep the condition honest so the loop still stops.
Off-by-one bites here. i <= names.length runs one past the end and names[i] is undefined. Prefer i < names.length when indexes start at 0.
while: keep going while this is true
while only has a condition. You manage the counter (or flag) yourself inside the body.
let left = 3;
while (left > 0) {
console.log("left", left);
left -= 1;
}
Scenario A — retries. “While the attempt failed and we still have tries left, try again.” You update tries inside the loop or you never leave.
Scenario B — the infinite loop. Forget left -= 1 and left > 0 stays true forever. The tab freezes. If that happens, close the tab or stop the script — then find what should change the condition.
Use while when you do not know the exact count up front, only the stop rule. Use for when the count is clear (list length, 0 to n).
do...while: run once, then check
do...while runs the body first, then checks the condition. So the body always runs at least once.
let answer;
do {
answer = "ok"; // in real UI this might come from a prompt or input
console.log("got", answer);
} while (answer !== "ok");
Scenario A — ask until valid. Show a form, read the value, ask again if empty. The first ask should happen even before you know if it is valid.
Scenario B — you only needed while. If the body must not run when the condition is already false, use while, not do...while. Beginners pick do...while less often; that is fine.
for...of: values from an array (and other iterables)
for...of gives you each value in order. For a beginner list, that is usually what you wanted.
const names = ["Asha", "Ravi", "Mia"];
for (const name of names) {
console.log(name);
}
Scenario A — render list text. Build a string or call a function once per item. You do not need the index unless you do.
Scenario B — you need the index too. Either keep a classic for, or use names.entries():
for (const [i, name] of names.entries()) {
console.log(i, name);
}
for...of also works on strings (each character) and later on other iterables. For this post, think “array values first.”
Do not use for...of on a plain object like { a: 1 }. Objects are not iterable that way — you will get a TypeError. For objects, use for...in or Object.keys (next section).
for...in: keys on an object
for...in walks enumerable property names (keys) on an object.
const user = { name: "Asha", city: "Pune" };
for (const key in user) {
console.log(key, user[key]);
}
That logs name Asha and city Pune.
Scenario A — print every field on a settings object. Keys are the labels; user[key] is the value.
Scenario B — the array trap. Arrays are objects with keys "0", "1", …. for...in on an array gives you indexes as strings, and it can also pick up extra properties if something was added to the array object. For array values, prefer for...of or a classic for. Save for...in for plain objects.
If you only want the object’s own keys (not inherited ones), a common safe pattern later is Object.keys(user) with for...of. For now, remember: object keys → for...in (or Object.keys); array values → for...of.
for...in vs for...of (the mix-up that wastes an afternoon)
| Loop | Best for | Gives you |
|---|---|---|
for...of | Arrays, strings | Each value |
for...in | Plain objects | Each key (name) |
classic for | When you need a numeric index / step | i and list[i] |
while | Unknown count, stop on a condition | You control the flag |
Scenario A — right tool. for (const clip of clips) to process each clip title.
Scenario B — wrong tool. for (const clip in clips) when clips is an array — clip is "0", "1", not the title. Your UI shows numbers or breaks lookups.
A tiny practice file
Save as loops.html. Click the button to list array values with for...of, then object keys with for...in.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Loops</title>
</head>
<body>
<button id="go">Run loops</button>
<pre id="out">Result will show here.</pre>
<script>
const goButton = document.getElementById("go");
const out = document.getElementById("out");
goButton.addEventListener("click", function () {
const names = ["Asha", "Ravi", "Mia"];
const user = { name: "Asha", city: "Pune" };
const lines = [];
lines.push("for...of on array:");
for (const name of names) {
lines.push("- " + name);
}
lines.push("for...in on object:");
for (const key in user) {
lines.push("- " + key + ": " + user[key]);
}
lines.push("classic for with index:");
for (let i = 0; i < names.length; i++) {
lines.push(i + " => " + names[i]);
}
out.textContent = lines.join("\n");
});
</script>
</body>
</html>
If you change the array for...of to for...in on purpose, the first section will print 0, 1, 2 instead of names — that is the lesson landing in the page.
Mistakes I see a lot
1. Infinite while. The condition never becomes false. Always change something the condition reads.
2. i <= array.length. One past the end. Use i < array.length.
3. for...in on arrays for values. You get indexes (and surprises). Use for...of.
4. for...of on a plain object. TypeError. Use for...in or Object.keys.
5. Reusing var i in nested loops the old way. Prefer let i in modern code so each loop owns its index.
6. Mutating a list while looping over it without care. Removing items mid-loop shifts indexes. Prefer building a new list, or learn filter later in the series.
What to try before the next post
- Log
0to4with a classicfor. - Walk
["a", "b", "c"]withfor...ofand again withfor...in— compare the logs. - Walk
{ x: 1, y: 2 }withfor...in. - Build the tiny HTML file and click once.
Next in this series: break, continue, and loop control — leaving early, skipping one pass, and when those keywords make code clearer or harder.
Try this next outside the series
Control flow starts to stick when it decides real UI and request outcomes.
- React forms without pain — watch conditionals and validation branches show up in a familiar app flow
- Diff Checker — compare before-and-after logic when you refactor branches