JavaScript for Loops
JavaScript for loops are not the dusty old version of array methods. They are the right tool when counting, indexes, boundaries, or step size are part of the problem. If the loop counter tells the story, hiding it inside a callback usually makes the code less honest, not more modern.
Basic syntax
A for loop puts the setup, condition, and update in one place:
for (let i = 0; i < 5; i++) {
console.log(i);
}
// 0
// 1
// 2
// 3
// 4
Those three pieces inside the parentheses are separated by semicolons:
| Part | Example | When it runs |
|---|---|---|
| Initializer | let i = 0 | Once, before the loop starts |
| Condition | i < 5 | Before each loop body run |
| Update | i++ | After each loop body run |
Read it as: start i at 0; while i is less than 5, run the body; after each body run, add 1 to i.
That is a lot of machinery for a tiny loop. The payoff is that the loop's boundaries are visible. When you need precise boundaries, visible is good.
How a for loop runs
The execution order is easier to see if you record each value:
const seen = [];
for (let i = 0; i < 3; i++) {
seen.push(i);
}
console.log(seen); // [0, 1, 2]
Here is the trace:
| Step | i before condition | Condition | Body runs | Update |
|---|---|---|---|---|
| 1 | 0 | 0 < 3 is true | push 0 | i becomes 1 |
| 2 | 1 | 1 < 3 is true | push 1 | i becomes 2 |
| 3 | 2 | 2 < 3 is true | push 2 | i becomes 3 |
| 4 | 3 | 3 < 3 is false | stop | none |
The body never runs with i equal to 3. The condition is checked before the body.
Counting with inclusive boundaries
The condition decides whether the final number is included.
for (let i = 1; i <= 5; i++) {
console.log(i);
}
// 1
// 2
// 3
// 4
// 5
Use <= when the final value is meant to be included. Use < when the final value is the first value outside the range.
That difference matters more than beginners expect:
// Runs 5 times: 0, 1, 2, 3, 4
for (let i = 0; i < 5; i++) {
console.log(i);
}
// Runs 6 times: 0, 1, 2, 3, 4, 5
for (let i = 0; i <= 5; i++) {
console.log(i);
}
Neither version is automatically wrong. The question is whether your boundary is a valid value or the stopping point just beyond the valid values.
Looping through arrays by index
Array indexes start at 0. The last valid index is one less than the length.
const names = ["Ada", "Grace", "Katherine"];
for (let i = 0; i < names.length; i++) {
console.log(`${i}: ${names[i]}`);
}
// 0: Ada
// 1: Grace
// 2: Katherine
This loop works because:
- the first index is
0 names.lengthis3- the last valid index is
2 i < names.lengthstops beforeibecomes3
Use this style when the index itself matters. Maybe you need to compare the current item with the previous item, build labels like "Item 3", or write into the same position in another array.
The classic off-by-one bug
This is the bug that keeps its little office in every beginner curriculum:
const lessons = ["Variables", "Strings", "Loops"];
const copied = [];
for (let i = 0; i <= lessons.length; i++) {
copied.push(lessons[i]);
}
console.log(copied);
// ["Variables", "Strings", "Loops", undefined]
The loop ran once too many times. lessons[3] does not exist, so JavaScript returns undefined.
Fix the condition:
const lessons = ["Variables", "Strings", "Loops"];
const copied = [];
for (let i = 0; i < lessons.length; i++) {
copied.push(lessons[i]);
}
console.log(copied);
// ["Variables", "Strings", "Loops"]
When looping over array indexes, i < array.length is the normal pattern. If you write i <= array.length, pause and prove that you really want one extra iteration. You usually do not.
Building a result array
A for loop can build a new array one item at a time:
const prices = [10, 20, 30];
const discounted = [];
for (let i = 0; i < prices.length; i++) {
discounted.push(prices[i] * 0.9);
}
console.log(discounted); // [9, 18, 27]
This is useful when the index matters, when the loop has multiple branches, or when you are learning how transformation works under the hood.
Once the pattern is simply "turn every item into another item," map is often clearer:
const prices = [10, 20, 30];
const discounted = prices.map((price) => price * 0.9);
That does not make the for loop wrong. It means the higher-level method gives the reader a more specific promise. The JavaScript Array Methods guide covers that output-shape decision in more detail.
Counting down and changing the step
A for loop does not have to count up by one:
for (let i = 5; i >= 1; i--) {
console.log(i);
}
// 5
// 4
// 3
// 2
// 1
It can also move by a larger step:
for (let i = 0; i <= 10; i += 2) {
console.log(i);
}
// 0
// 2
// 4
// 6
// 8
// 10
The condition and update need to agree. This loop never runs:
for (let i = 10; i < 0; i--) {
console.log(i);
}
i starts at 10, and 10 < 0 is already false. JavaScript checks the condition before the first body run, shrugs politely, and moves on.
break and continue
break exits the loop immediately:
const tasks = ["write notes", "fix login", "fix checkout", "ship"];
let firstFix = "";
for (let i = 0; i < tasks.length; i++) {
if (tasks[i].includes("fix")) {
firstFix = tasks[i];
break;
}
}
console.log(firstFix); // "fix login"
That loop stops after the first match. Without break, "fix checkout" would replace "fix login".
continue skips the rest of the current iteration and moves to the next one:
const scores = [82, -1, 94, 120, 75];
const validScores = [];
for (let i = 0; i < scores.length; i++) {
const score = scores[i];
if (score < 0 || score > 100) {
continue;
}
validScores.push(score);
}
console.log(validScores); // [82, 94, 75]
Use continue for "skip this item." Use break for "stop the whole loop."
for versus for...of
Use a classic for loop when the index, counter, or step size matters:
const letters = ["a", "b", "c"];
for (let i = 0; i < letters.length; i++) {
console.log(i, letters[i]);
}
Use for...of when you only need each value:
const letters = ["a", "b", "c"];
for (const letter of letters) {
console.log(letter);
}
The JavaScript for...of Loop guide covers iterables, strings, Sets, Maps, and async loops. The short version: for is about control over positions; for...of is about values.
Key Takeaways
- Use JavaScript for loops when counting, indexes, boundaries, or step size are part of the logic.
- For array indexes,
i < array.lengthavoids the classic extraundefinedvalue. breakstops the loop;continueskips only the current iteration.- If you only need values,
for...ofis usually cleaner than manual indexing. - Array methods are not replacements for all loops. They are better when the output shape is the real point.