Knowledge Base/concepts/JavaScript for Loops

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:

PartExampleWhen it runs
Initializerlet i = 0Once, before the loop starts
Conditioni < 5Before each loop body run
Updatei++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:

Stepi before conditionConditionBody runsUpdate
100 < 3 is truepush 0i becomes 1
211 < 3 is truepush 1i becomes 2
322 < 3 is truepush 2i becomes 3
433 < 3 is falsestopnone

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.length is 3
  • the last valid index is 2
  • i < names.length stops before i becomes 3

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.length avoids the classic extra undefined value.
  • break stops the loop; continue skips only the current iteration.
  • If you only need values, for...of is usually cleaner than manual indexing.
  • Array methods are not replacements for all loops. They are better when the output shape is the real point.
Share this article:
javascriptfundamentalsloopscontrol-flow