Skip to content

For Loops: The Art of Iteration

Imagine you need to send New Year greeting emails to 100 employees in your company. You wouldn't manually copy and paste 100 times. Instead, you'd create an email template and have the program automatically iterate through the employee list, sending a personalized email to each person. This "repeat the same operation" scenario is where loop statements shine. In JavaScript, the for loop family is the most commonly used iteration tool.

Traditional For Loops

The most classic for loop consists of three parts: initialization, condition checking, and update expression. Its structure is like a precise clock mechanism, turning round by round according to set rules:

javascript
for (initialization; condition; increment) {
  // Loop body
}

Let's start with a simple example, printing numbers 1 to 5:

javascript
for (let i = 1; i <= 5; i++) {
  console.log(i);
}

// Output:
// 1
// 2
// 3
// 4
// 5

The execution process of this loop is:

  1. Initialization (let i = 1): Executed once before the loop starts, creating counter variable i and assigning it the value 1
  2. Condition Check (i <= 5): Checked before each loop iteration; if true, execute the loop body, otherwise end the loop
  3. Execute Loop Body: Output the current value of i
  4. Update (i++): After each loop body execution, increment i by 1
  5. Return to step 2 and continue the next iteration

When i becomes 6, the condition i <= 5 is false, and the loop ends.

Traversing Arrays

One of the most common uses of for loops is traversing arrays. By accessing array elements through indexes, we can process each piece of data:

javascript
let fruits = ["apple", "banana", "orange", "grape", "mango"];

for (let i = 0; i < fruits.length; i++) {
  console.log(`Fruit ${i + 1}: ${fruits[i]}`);
}

// Output:
// Fruit 1: apple
// Fruit 2: banana
// Fruit 3: orange
// Fruit 4: grape
// Fruit 5: mango

Note that here we use i < fruits.length instead of i <= fruits.length because array indices start at 0. If an array has 5 elements, the valid indices are 0 to 4; using <= would try to access the non-existent index 5.

Flexible Loop Control

All three parts of a for loop are optional, providing great flexibility:

javascript
// Initialize outside the loop
let i = 0;
for (; i < 5; i++) {
  console.log(i);
}

// Update inside the loop body
let j = 0;
for (; j < 5; ) {
  console.log(j);
  j++;
}

// All parts omitted (need manual break, otherwise infinite loop)
let k = 0;
for (;;) {
  if (k >= 5) break;
  console.log(k);
  k++;
}

Although all these writing styles are legal, the standard three-part loop is clearest and should be used preferentially.

Decrement and Step Control

Loops don't necessarily have to increment; they can also decrement or use other steps:

javascript
// Counting down
for (let i = 5; i >= 1; i--) {
  console.log(`Countdown: ${i}`);
}
// Output: Countdown: 5, 4, 3, 2, 1

// Skip two steps each time
for (let i = 0; i < 10; i += 2) {
  console.log(i);
}
// Output: 0, 2, 4, 6, 8

// Process even indices of an array
let numbers = [10, 20, 30, 40, 50, 60];
for (let i = 0; i < numbers.length; i += 2) {
  console.log(numbers[i]);
}
// Output: 10, 30, 50

Reverse traversal is useful in certain scenarios, such as processing from the end of an array or deleting elements during traversal:

javascript
let items = ["a", "b", "c", "d", "e"];

// Traverse from back to front and delete to avoid index confusion
for (let i = items.length - 1; i >= 0; i--) {
  if (items[i] === "c") {
    items.splice(i, 1);
  }
}

console.log(items); // ["a", "b", "d", "e"]

Nested Loops

When dealing with multidimensional data or needing combined traversal, you can use nested loops:

javascript
// Create multiplication table
for (let i = 1; i <= 3; i++) {
  for (let j = 1; j <= 3; j++) {
    console.log(`${i} × ${j} = ${i * j}`);
  }
  console.log("---");
}

// Output:
// 1 × 1 = 1
// 1 × 2 = 2
// 1 × 3 = 3
// ---
// 2 × 1 = 2
// 2 × 2 = 4
// 2 × 3 = 6
// ---
// 3 × 1 = 3
// 3 × 2 = 6
// 3 × 3 = 9
// ---

Traverse two-dimensional arrays:

javascript
let matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
];

for (let row = 0; row < matrix.length; row++) {
  for (let col = 0; col < matrix[row].length; col++) {
    console.log(`matrix[${row}][${col}] = ${matrix[row][col]}`);
  }
}

The time complexity of nested loops grows rapidly. Two-level nesting is O(n²), three-level is O(n³). Pay special attention to performance when dealing with large datasets.

For...of Loops: Traversing Iterables

ES6 introduced for...of loops, specifically designed to traverse iterables (such as arrays, strings, Set, Map, etc.). Its syntax is more concise and doesn't require managing indexes:

javascript
let colors = ["red", "green", "blue"];

for (let color of colors) {
  console.log(color);
}

// Output:
// red
// green
// blue

Compared to traditional for loops, for...of focuses more on "what" rather than "how." You don't need to write colors[i]; you directly get each element. This declarative style is more readable and less error-prone.

Traversing Strings

Strings are also iterable and can be traversed character by character:

javascript
let message = "Hello";

for (let char of message) {
  console.log(char);
}

// Output:
// H
// e
// l
// l
// o

This is particularly useful for handling Unicode characters because for...of correctly identifies Unicode code points without splitting some special characters into multiple parts.

Traversing Set and Map

javascript
// Set
let uniqueNumbers = new Set([1, 2, 3, 2, 1]);

for (let num of uniqueNumbers) {
  console.log(num);
}
// Output: 1, 2, 3

// Map
let userRoles = new Map([
  ["Alice", "admin"],
  ["Bob", "editor"],
  ["Charlie", "viewer"],
]);

for (let [name, role] of userRoles) {
  console.log(`${name}: ${role}`);
}
// Output:
// Alice: admin
// Bob: editor
// Charlie: viewer

Note that when traversing Maps, we use destructuring assignment [name, role] to directly get key-value pairs.

Limitations of For...of

for...of cannot be directly used on plain objects because plain objects are not iterable by default:

javascript
let person = { name: "Alice", age: 30 };

// ❌ This will throw an error: person is not iterable
for (let item of person) {
  console.log(item);
}

// ✅ Can traverse object values
for (let value of Object.values(person)) {
  console.log(value);
}
// Output: Alice, 30

// ✅ Or traverse key-value pairs
for (let [key, value] of Object.entries(person)) {
  console.log(`${key}: ${value}`);
}
// Output:
// name: Alice
// age: 30

For...in Loops: Traversing Object Properties

for...in loops are used to traverse enumerable properties of an object (including inherited properties):

javascript
let car = {
  brand: "Tesla",
  model: "Model 3",
  year: 2023,
  color: "white",
};

for (let key in car) {
  console.log(`${key}: ${car[key]}`);
}

// Output:
// brand: Tesla
// model: Model 3
// year: 2023
// color: white

For...in with Arrays

Technically, for...in can also be used with arrays, but it's not recommended:

javascript
let numbers = [10, 20, 30];

// ❌ Not recommended: for...in traversing arrays
for (let index in numbers) {
  console.log(typeof index); // string!
  console.log(numbers[index]);
}

The problems are:

  1. for...in traverses indices as strings, not numbers
  2. It traverses all enumerable properties, including any custom properties you might have added to the array
  3. Traversal order is not guaranteed (although most engines follow index order)

For arrays, you should use traditional for loops, for...of, or array methods:

javascript
let numbers = [10, 20, 30];

// ✅ Recommended approach
for (let num of numbers) {
  console.log(num);
}

// Or
numbers.forEach((num) => console.log(num));

Filtering Inherited Properties

for...in traverses both the object's own and inherited enumerable properties. If you only want to process own properties, use hasOwnProperty:

javascript
let animal = { eats: true };
let rabbit = Object.create(animal);
rabbit.jumps = true;

for (let prop in rabbit) {
  console.log(`${prop}: ${rabbit[prop]}`);
}
// Output:
// jumps: true
// eats: true (inherited)

// Only traverse own properties
for (let prop in rabbit) {
  if (rabbit.hasOwnProperty(prop)) {
    console.log(`${prop}: ${rabbit[prop]}`);
  }
}
// Output:
// jumps: true

Or more concisely use Object.keys(), Object.values(), or Object.entries(), which only return own properties:

javascript
let rabbit = { jumps: true, color: "white" };

// Only traverse keys of own properties
for (let key of Object.keys(rabbit)) {
  console.log(key);
}

// Only traverse values of own properties
for (let value of Object.values(rabbit)) {
  console.log(value);
}

// Traverse key-value pairs
for (let [key, value] of Object.entries(rabbit)) {
  console.log(`${key}: ${value}`);
}

Comparison of Three For Loops

Featureforfor...offor...in
Main UseGeneral loop, precise controlTraverse iterables (arrays, strings, Set, Map, etc.)Traverse object properties
Applies ToAny scenarioArrays, strings, Set, Map, etc.Objects
Content GetNeed index accessDirectly get element valuesGet property keys
Index/Key TypeNumber-String
PerformanceFastestSlightly slowerSlower
ReadabilityModerateHigh (concise)High (clear)

When to use:

  • for: Need precise loop control, such as custom step sizes, complex conditions, performance-critical scenarios
  • for...of: Traverse arrays, strings, and other iterables, focusing on element values
  • for...in: Traverse object properties

Practical Application Scenarios

1. Data Transformation

javascript
let prices = [19.99, 29.99, 39.99, 49.99];
let pricesWithTax = [];

// Calculate prices including tax
for (let i = 0; i < prices.length; i++) {
  pricesWithTax[i] = prices[i] * 1.1; // 10% tax
}

console.log(pricesWithTax); // [21.989, 32.989, 43.989, 54.989]

// Using for...of is more concise
let pricesWithTax2 = [];
for (let price of prices) {
  pricesWithTax2.push(price * 1.1);
}

2. Finding Elements

javascript
let users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
  { id: 3, name: "Charlie" },
];

function findUserById(id) {
  for (let user of users) {
    if (user.id === id) {
      return user;
    }
  }
  return null; // Not found
}

console.log(findUserById(2)); // { id: 2, name: "Bob" }
console.log(findUserById(5)); // null

3. Accumulative Calculation

javascript
let scores = [85, 92, 78, 95, 88];
let total = 0;

for (let score of scores) {
  total += score;
}

let average = total / scores.length;
console.log(`Average score: ${average}`); // Average score: 87.6

4. Building HTML

javascript
let items = ["Home", "About", "Services", "Contact"];
let navHTML = "<nav><ul>";

for (let item of items) {
  navHTML += `<li><a href="#${item.toLowerCase()}">${item}</a></li>`;
}

navHTML += "</ul></nav>";
console.log(navHTML);
// <nav><ul><li><a href="#home">Home</a></li><li><a href="#about">About</a></li>...</ul></nav>

5. Batch Operations

javascript
let tasks = [
  { id: 1, title: "Write report", completed: false },
  { id: 2, title: "Review code", completed: false },
  { id: 3, title: "Update docs", completed: false },
];

// Mark all tasks as completed
for (let task of tasks) {
  task.completed = true;
  console.log(`Marked "${task.title}" as completed`);
}

Performance Optimization Tips

1. Cache Array Length

In traditional for loops, if the loop condition is i < array.length, the length property is accessed on every iteration. For large arrays, you can cache this value:

javascript
let data = new Array(1000000).fill(0);

// ❌ Access length every time
for (let i = 0; i < data.length; i++) {
  // Process data[i]
}

// ✅ Cache length
let len = data.length;
for (let i = 0; i < len; i++) {
  // Process data[i]
}

However, modern JavaScript engines usually optimize this situation, so the performance difference might be minimal. Readability is often more important.

2. Choose Appropriate Loop Type

For simple array traversal, although for...of is slightly slower, the difference is usually negligible, and the readability improvement is tangible. Only consider using traditional for loops in performance-critical code paths.

3. Avoid Expensive Operations in Loops

javascript
// ❌ Query DOM every time
for (let i = 0; i < 100; i++) {
  document.getElementById("output").innerHTML += i + "<br>";
}

// ✅ Build string first, then update DOM once
let output = "";
for (let i = 0; i < 100; i++) {
  output += i + "<br>";
}
document.getElementById("output").innerHTML = output;

Common Pitfalls

1. Modifying Array While Traversing

javascript
let numbers = [1, 2, 3, 4, 5];

// ❌ Dangerous: deleting elements while traversing
for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    numbers.splice(i, 1); // Delete even numbers
  }
}
console.log(numbers); // [1, 3, 5] but might skip certain elements

// ✅ Traverse from back to front
let numbers2 = [1, 2, 3, 4, 5];
for (let i = numbers2.length - 1; i >= 0; i--) {
  if (numbers2[i] % 2 === 0) {
    numbers2.splice(i, 1);
  }
}
console.log(numbers2); // [1, 3, 5]

2. Closure Traps (var vs let)

javascript
// ❌ Using var
for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i); // Outputs 3, 3, 3
  }, 100);
}

// ✅ Using let
for (let j = 0; j < 3; j++) {
  setTimeout(function () {
    console.log(j); // Outputs 0, 1, 2
  }, 100);
}

When using var, all iterations share the same i variable. When using let, each iteration has its own j copy.

3. For...in Traversal of Arrays Pitfall

javascript
let arr = ["a", "b", "c"];
arr.customProp = "custom";

// ❌ for...in will traverse custom properties
for (let index in arr) {
  console.log(arr[index]);
}
// Output: a, b, c, custom

// ✅ for...of only traverses array elements
for (let item of arr) {
  console.log(item);
}
// Output: a, b, c

Summary

For loops are indispensable tools in JavaScript, and mastering them allows you to efficiently handle various iteration scenarios. Traditional for loops provide maximum flexibility and control, for...of makes traversing iterables concise and elegant, and for...in is the go-to tool for traversing object properties.

Key points:

  • Traditional for loops are suitable for scenarios requiring precise control
  • for...of is the preferred choice for traversing arrays, strings, and other iterables with concise syntax
  • for...in is used for traversing object properties, not recommended for arrays
  • Pay attention to time complexity with nested loops
  • Avoid modifying arrays while traversing them
  • Use let instead of var to avoid closure issues
  • Performance optimization should be done after confirming bottlenecks; readability is equally important

Choosing appropriate loop types can make your code both efficient and readable. As you gain more practice, you'll become more proficient in using these tools.