Labs ICT
⭐ Pro Login

Loops

JavaScript loops provide powerful ways to repeat code execution, iterate over data structures, and control program flow. Understanding different loop types and their appropriate use cases is essential for writing efficient and maintainable code.

In this tutorial, we'll explore various JavaScript loop constructs, iteration patterns, performance considerations, and best practices for looping in modern JavaScript.

for Loop

The traditional for loop provides complete control over iteration with initialization, condition, and increment expressions.

Basic for Loop

// Basic for loop syntax
for (initialization; condition; increment) {
  // Code to execute
}

// Count from 1 to 10
for (let i = 1; i <= 10; i++) {
  console.log(i);
}

// Count backwards
for (let i = 10; i >= 1; i--) {
  console.log(i);
}

// Step by 2
for (let i = 0; i <= 10; i += 2) {
  console.log(i);
}

// Multiple variables
for (let i = 0, j = 10; i < j; i++, j--) {
  console.log(i, j);
}

Loop Control

// break - exit loop early
for (let i = 1; i <= 10; i++) {
  if (i === 5) {
    break; // Exit loop when i equals 5
  }
  console.log(i);
}
// Output: 1, 2, 3, 4

// continue - skip to next iteration
for (let i = 1; i <= 10; i++) {
  if (i === 5) {
    continue; // Skip when i equals 5
  }
  console.log(i);
}
// Output: 1, 2, 3, 4, 6, 7, 8, 9, 10

// Nested loops with break/continue
for (let i = 1; i <= 3; i++) {
  for (let j = 1; j <= 3; j++) {
    if (i === 2 && j === 2) {
      continue;
    }
    console.log(i, j);
  }
}

for Loop with Arrays

// Traditional array iteration
const fruits = ['apple', 'banana', 'orange'];
for (let i = 0; i < fruits.length; i++) {
  console.log(fruits[i]);
}

// Cache length for performance
const numbers = [1, 2, 3, 4, 5];
const len = numbers.length;
for (let i = 0; i < len; i++) {
  console.log(numbers[i]);
}

// Modify array during iteration
const values = [1, 2, 3, 4, 5];
for (let i = 0; i < values.length; i++) {
  values[i] = values[i] * 2;
}
console.log(values); // [2, 4, 6, 8, 10]

while Loop

The while loop continues execution as long as the condition remains true, useful for unknown iteration counts.

Basic while Loop

// Basic while loop
let count = 1;
while (count <= 5) {
  console.log(count);
  count++;
}

// User input simulation
let userInput;
while (userInput !== 'quit') {
  userInput = prompt('Enter "quit" to exit:');
  console.log('You entered:', userInput);
}

// Random number generation
let randomNum;
let attempts = 0;
while (randomNum !== 7 && attempts < 10) {
  randomNum = Math.floor(Math.random() * 10) + 1;
  attempts++;
  console.log(`Attempt ${attempts}: ${randomNum}`);
}

Infinite Loop Prevention

// Always ensure loop can exit
let counter = 0;
while (counter < 10) {
  console.log(counter);
  counter++; // Don't forget increment!
}

// Use break for conditional exit
let searchValue = 42;
let found = false;
let attempts = 0;
const maxAttempts = 100;

while (!found && attempts < maxAttempts) {
  const random = Math.floor(Math.random() * 100);
  attempts++;
  
  if (random === searchValue) {
    found = true;
    console.log(`Found ${searchValue} after ${attempts} attempts`);
  }
}

if (!found) {
  console.log(`Did not find ${searchValue} after ${maxAttempts} attempts`);
}

do...while Loop

The do...while loop executes at least once before checking the condition.

Basic do...while Loop

// do...while loop - always executes once
let number;
do {
  number = Math.floor(Math.random() * 10) + 1;
  console.log('Generated number:', number);
} while (number !== 7);

// Menu-driven program
let choice;
do {
  console.log('1. Add');
  console.log('2. Subtract');
  console.log('3. Exit');
  choice = prompt('Enter your choice (1-3):');
  
  if (choice === '1') {
    console.log('Add operation');
  } else if (choice === '2') {
    console.log('Subtract operation');
  }
} while (choice !== '3');

// Input validation
let userInput;
do {
  userInput = prompt('Enter a positive number:');
  if (userInput === null) break; // User cancelled
  userInput = Number(userInput);
} while (isNaN(userInput) || userInput <= 0);

if (userInput && userInput > 0) {
  console.log('Valid positive number:', userInput);
}

for...in Loop

The for...in loop iterates over the properties of an object.

Object Properties

// Iterate over object properties
const person = {
  name: 'John',
  age: 30,
  city: 'New York'
};

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

// Check for own properties only
for (const key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(`${key}: ${person[key]}`);
  }
}

// Modern alternative: Object.keys()
Object.keys(person).forEach(key => {
  console.log(`${key}: ${person[key]}`);
});

// Object.entries() for key-value pairs
Object.entries(person).forEach(([key, value]) => {
  console.log(`${key}: ${value}`);
});

Array Indexes

// for...in with arrays (not recommended)
const fruits = ['apple', 'banana', 'orange'];

for (const index in fruits) {
  console.log(`${index}: ${fruits[index]}`);
}

// Problems with for...in and arrays:
// 1. Iterates in arbitrary order
// 2. Includes inherited properties
// 3. Indexes are strings, not numbers

// Better alternatives:
// for loop
for (let i = 0; i < fruits.length; i++) {
  console.log(`${i}: ${fruits[i]}`);
}

// for...of loop (ES6)
for (const [index, fruit] of fruits.entries()) {
  console.log(`${index}: ${fruit}`);
}

// forEach method
fruits.forEach((fruit, index) => {
  console.log(`${index}: ${fruit}`);
});

for...of Loop (ES6)

The for...of loop iterates over iterable objects like arrays, strings, Maps, and Sets.

Array Iteration

// for...of with arrays
const numbers = [1, 2, 3, 4, 5];

for (const number of numbers) {
  console.log(number);
}

// With index using entries()
for (const [index, number] of numbers.entries()) {
  console.log(`${index}: ${number}`);
}

// Iterate over array of objects
const users = [
  { name: 'John', age: 30 },
  { name: 'Jane', age: 25 },
  { name: 'Bob', age: 35 }
];

for (const user of users) {
  console.log(`${user.name} is ${user.age} years old`);
}

// Destructuring in loop
for (const { name, age } of users) {
  console.log(`${name} is ${age} years old`);
}

String Iteration

// Iterate over string characters
const text = "Hello World";

for (const char of text) {
  console.log(char);
}

// With index
for (const [index, char] of text.entries()) {
  console.log(`${index}: ${char}`);
}

// Unicode characters
const emoji = "šŸ‘‹ Hello šŸ‘‹";
for (const char of emoji) {
  console.log(char);
}

Map and Set Iteration

// Map iteration
const map = new Map([
  ['name', 'John'],
  ['age', 30],
  ['city', 'New York']
]);

for (const [key, value] of map) {
  console.log(`${key}: ${value}`);
}

// Set iteration
const set = new Set([1, 2, 3, 4, 5]);

for (const value of set) {
  console.log(value);
}

// Iterate over Map keys
for (const key of map.keys()) {
  console.log(key);
}

// Iterate over Map values
for (const value of map.values()) {
  console.log(value);
}

Array Iteration Methods

Built-in array methods provide functional ways to iterate over arrays.

forEach Method

// forEach method
const numbers = [1, 2, 3, 4, 5];

numbers.forEach((number, index) => {
  console.log(`${index}: ${number}`);
});

// forEach with side effects
const results = [];
numbers.forEach(number => {
  results.push(number * 2);
});
console.log(results); // [2, 4, 6, 8, 10]

// forEach doesn't return anything
const doubled = numbers.forEach(n => n * 2);
console.log(doubled); // undefined

// Use map for transformation
const doubledMap = numbers.map(n => n * 2);
console.log(doubledMap); // [2, 4, 6, 8, 10]

map Method

// map - transform each element
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// Map to different types
const strings = numbers.map(n => `Number: ${n}`);
console.log(strings); // ["Number: 1", "Number: 2", ...]

// Map with index
const indexed = numbers.map((n, i) => `${i}: ${n}`);
console.log(indexed); // ["0: 1", "1: 2", ...]

// Map objects
const users = [
  { id: 1, name: 'John' },
  { id: 2, name: 'Jane' }
];

const names = users.map(user => user.name);
console.log(names); // ["John", "Jane"]

const userElements = users.map(user => 
  `<div>${user.name}</div>`
);

filter Method

// filter - select elements
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const evenNumbers = numbers.filter(n => n % 2 === 0);
console.log(evenNumbers); // [2, 4, 6, 8, 10]

const greaterThanFive = numbers.filter(n => n > 5);
console.log(greaterThanFive); // [6, 7, 8, 9, 10]

// Filter objects
const users = [
  { name: 'John', age: 30, active: true },
  { name: 'Jane', age: 25, active: false },
  { name: 'Bob', age: 35, active: true }
];

const activeUsers = users.filter(user => user.active);
console.log(activeUsers); // [{name: 'John', age: 30, active: true}, ...]

// Chaining methods
const activeOver30 = users
  .filter(user => user.active)
  .filter(user => user.age > 30);
console.log(activeOver30); // [{name: 'Bob', age: 35, active: true}]

reduce Method

// reduce - reduce array to single value
const numbers = [1, 2, 3, 4, 5];

const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15

// Find maximum
const max = numbers.reduce((max, n) => Math.max(max, n));
console.log(max); // 5

// Build object from array
const fruits = ['apple', 'banana', 'orange'];
const fruitObj = fruits.reduce((obj, fruit) => {
  obj[fruit] = fruit.length;
  return obj;
}, {});
console.log(fruitObj); // {apple: 5, banana: 6, orange: 6}

// Group by property
const people = [
  { name: 'John', department: 'IT' },
  { name: 'Jane', department: 'HR' },
  { name: 'Bob', department: 'IT' }
];

const byDepartment = people.reduce((groups, person) => {
  const dept = person.department;
  if (!groups[dept]) {
    groups[dept] = [];
  }
  groups[dept].push(person.name);
  return groups;
}, {});
console.log(byDepartment); // {IT: ['John', 'Bob'], HR: ['Jane']}

Loop Performance

Understanding performance characteristics of different loop types.

Performance Comparison

// Performance test
const largeArray = Array(1000000).fill(0).map((_, i) => i);

// for loop (fastest)
console.time('for');
let sum1 = 0;
for (let i = 0; i < largeArray.length; i++) {
  sum1 += largeArray[i];
}
console.timeEnd('for');

// while loop
console.time('while');
let sum2 = 0;
let i = 0;
while (i < largeArray.length) {
  sum2 += largeArray[i];
  i++;
}
console.timeEnd('while');

// for...of loop
console.time('for...of');
let sum3 = 0;
for (const num of largeArray) {
  sum3 += num;
}
console.timeEnd('for...of');

// forEach
console.time('forEach');
let sum4 = 0;
largeArray.forEach(num => {
  sum4 += num;
});
console.timeEnd('forEach');

// for...in (slowest, don't use for arrays)
console.time('for...in');
let sum5 = 0;
for (const index in largeArray) {
  sum5 += largeArray[index];
}
console.timeEnd('for...in');

Optimization Techniques

// Cache array length
const array = [1, 2, 3, 4, 5];
const len = array.length; // Cache length
for (let i = 0; i < len; i++) {
  // Process array[i]
}

// Reverse iteration (slightly faster)
for (let i = array.length - 1; i >= 0; i--) {
  // Process array[i]
}

// Use appropriate loop type
// Best for performance: for loop
// Best for readability: for...of
// Best for arrays: forEach/map/filter/reduce

// Avoid unnecessary loops
// Bad: O(n²)
const items = [1, 2, 3, 4, 5];
for (let i = 0; i < items.length; i++) {
  for (let j = 0; j < items.length; j++) {
    // O(n²) operations
  }
}

// Good: Use Set for lookups
const itemSet = new Set(items);
for (const item of items) {
  if (itemSet.has(item)) {
    // O(1) lookup
  }
}

Loop Patterns

Common patterns and techniques for effective looping.

Early Exit Patterns

// Find first matching element
function findFirst(array, predicate) {
  for (const item of array) {
    if (predicate(item)) {
      return item;
    }
  }
  return null;
}

// Check if any element matches
function anyMatch(array, predicate) {
  for (const item of array) {
    if (predicate(item)) {
      return true;
    }
  }
  return false;
}

// Check if all elements match
function allMatch(array, predicate) {
  for (const item of array) {
    if (!predicate(item)) {
      return false;
    }
  }
  return true;
}

Accumulation Patterns

// Count occurrences
function countOccurrences(array, target) {
  let count = 0;
  for (const item of array) {
    if (item === target) {
      count++;
    }
  }
  return count;
}

// Group by property
function groupBy(array, key) {
  const groups = {};
  for (const item of array) {
    const group = item[key];
    if (!groups[group]) {
      groups[group] = [];
    }
    groups[group].push(item);
  }
  return groups;
}

// Find min/max
function findMinMax(array) {
  if (array.length === 0) return { min: null, max: null };
  
  let min = array[0];
  let max = array[0];
  
  for (let i = 1; i < array.length; i++) {
    const value = array[i];
    if (value < min) min = value;
    if (value > max) max = value;
  }
  
  return { min, max };
}

Nested Loop Patterns

// Matrix operations
function transpose(matrix) {
  const result = [];
  for (let i = 0; i < matrix[0].length; i++) {
    result[i] = [];
    for (let j = 0; j < matrix.length; j++) {
      result[i][j] = matrix[j][i];
    }
  }
  return result;
}

// Cartesian product
function cartesianProduct(arrays) {
  const result = [];
  
  function backtrack(current, index) {
    if (index === arrays.length) {
      result.push([...current]);
      return;
    }
    
    for (const item of arrays[index]) {
      current.push(item);
      backtrack(current, index + 1);
      current.pop();
    }
  }
  
  backtrack([], 0);
  return result;
}

// Find pairs
function findPairs(array, sum) {
  const pairs = [];
  for (let i = 0; i < array.length; i++) {
    for (let j = i + 1; j < array.length; j++) {
      if (array[i] + array[j] === sum) {
        pairs.push([array[i], array[j]]);
      }
    }
  }
  return pairs;
}

Async Iteration

Looping with asynchronous operations.

async/await with for...of

// Async for...of loop
async function processItems(items) {
  for (const item of items) {
    const result = await processData(item);
    console.log('Processed:', result);
  }
}

// With index
async function processItemsWithIndex(items) {
  for (const [index, item] of items.entries()) {
    const result = await processData(item);
    console.log(`Item ${index}:`, result);
  }
}

// Parallel processing
async function processItemsParallel(items) {
  const promises = items.map(item => processData(item));
  const results = await Promise.all(promises);
  
  for (const [index, result] of results.entries()) {
    console.log(`Item ${index}:`, result);
  }
}

Async Generators

// Async generator
async function* asyncGenerator(array) {
  for (const item of array) {
    const result = await processData(item);
    yield result;
  }
}

// Using async generator
async function processWithGenerator(items) {
  const generator = asyncGenerator(items);
  
  for await (const result of generator) {
    console.log('Processed:', result);
  }
}

// Async iteration protocol
const asyncIterable = {
  async *[Symbol.asyncIterator]() {
    for (let i = 1; i <= 5; i++) {
      await delay(1000); // Simulate async operation
      yield i;
    }
  }
};

// Consume async iterable
async function consumeAsyncIterable() {
  for await (const value of asyncIterable) {
    console.log(value);
  }
}

Loop Best Practices

Guidelines for writing clean and efficient loop code.

Choose the Right Loop

// Use for...of for arrays and iterables
const array = [1, 2, 3, 4, 5];
for (const item of array) {
  console.log(item);
}

// Use for...in for objects (with hasOwnProperty)
const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(key, obj[key]);
  }
}

// Use Object.keys() for objects (better)
Object.keys(obj).forEach(key => {
  console.log(key, obj[key]);
});

// Use forEach for side effects
numbers.forEach(num => console.log(num));

// Use map for transformation
const doubled = numbers.map(num => num * 2);

// Use filter for selection
const evens = numbers.filter(num => num % 2 === 0);

// Use reduce for aggregation
const sum = numbers.reduce((acc, num) => acc + num, 0);

Loop Safety

// Always include increment/decrement
for (let i = 0; i < 10; i++) {
  // Don't forget i++!
}

// Use let instead of var to avoid scope issues
for (let i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i); // Works with let
  }, 100);
}

// Avoid modifying array during iteration
const numbers = [1, 2, 3, 4, 5];
for (let i = 0; i < numbers.length; i++) {
  // numbers.splice(i, 1); // Don't modify during iteration!
}

// Instead, create a new array or iterate backwards
for (let i = numbers.length - 1; i >= 0; i--) {
  // Safe to modify
}

Readability

// Use descriptive variable names
for (let index = 0; index < items.length; index++) {
  const currentItem = items[index];
  processItem(currentItem, index);
}

// Keep loops simple and focused
// Bad: Complex logic in loop
for (let i = 0; i < items.length; i++) {
  // 50 lines of complex logic
}

// Good: Extract to function
for (const item of items) {
  processComplexItem(item);
}

// Use functional methods when appropriate
const results = items
  .filter(item => item.isValid)
  .map(item => processItem(item))
  .filter(result => result.success);

Summary

Key Takeaways

  • for loop provides complete control over iteration
  • while/do...while loops are for unknown iteration counts
  • for...of is the modern way to iterate over iterables
  • for...in is for object properties, not arrays
  • Array methods provide functional iteration patterns

Best Practices

  • Use for...of for arrays and iterables
  • Use Object.keys() for object iteration
  • Prefer functional methods for transformation
  • Always include loop control statements
  • Choose the right loop for the use case

Common Pitfalls

  • Using for...in for arrays (performance issues)
  • Forgetting increment/decrement in while loops
  • Modifying arrays during iteration
  • Using var instead of let in loops
  • Not breaking early when appropriate