JavaScript loops explained, and best practices

Loops, the most commonly used construct in programming languages, are used to help run a piece of code multiple times. In JavaScript, where everything is an object, there are many options available to loop over objects. With so many options available, it can be confusing to know which loop to use where. This article aims to simplify this confusion by providing a walkthrough of the most popular loops in JavaScript, along with some real-world examples.

JavaScript Loops Explained, And Best Practices

Let’s dive in!

for loop

The for loop is the most commonly used loop to iterate over arrays, strings, or any array-like objects is the for loop. Everyone learns for on their first day of learning JavaScript!

A for loop in JavaScript follows this format:

for (initialization; condition; final-expression) {
  // body
}

Here’s an example of the loop in action:

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

for(let i=0;i<numbers.length;i++) {
  // do something...
  console.log(numbers[i]);
}

You can use for when you want low-level control over a loop.

Though it’s fairly basic, there are a few things a developer should know about the for loop:

Using a break keyword in a for loop breaks the execution, and the code will exit the for loop:

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

for(let i=0 ; i < numbers.length ;i++) {
  if(i === 3) break;
  console.log(numbers[i]);
}

The above for loop will exit when iterating over the third number in the array. The break statement is useful when you want to stop processing elements in an array or array-like data structures when a certain condition is met.

Using the continuekeyword in a for loop skips to the next element:

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

for(let i=0 ; i < numbers.length ;i++) {
  if(i === 3) continue;
  console.log(numbers[i]);
}

The above for loop will skip logging 3 to the console because the continue keyword skips to the next element in an array. All statements below the continue keyword are skipped. continue is useful when you selectively process elements in an array or array-like data structure.

All expressions in the loop are optional:

Both for loops below are valid for loops in JavaScript:

let i = 0;
for (; i < 3;) {
  console.log(i++); // 0 1 2
}

for(;;;) {
  console.log("This will print forever.....")  //infinite loop
}

for...of loop

This loop is a variation of a for loop that was recently added to JavaScript. It is useful for iterating over arrays, strings, sets, maps, typed arrays, etc., — basically any object that implements the [Symbol.iterator] function.

Following the same example, this is how you would use a for…of loop:

const numbers = [1,2,3,4,5]
for(const num of numbers) {
    console.log(num) // 1 2 3 4 5
}

The break and continue keywords work similarly for the for…of loop.

Here are a few things to keep in mind when using this loop:

The iterated values are copies of the original ones, not references:

const numbers = [1,2,3,4]

for(let num of numbers) {
  num = num + 2; // this doesn't change the values in the numbers array
}

console.log(numbers) // 1,2,3,4

for...of can’t access the index:

Unlike the for loop, the for…of loop doesn’t pass the index of the current item in the collection. You would have to manually keep track of the index:

const numbers = [1,2,3,4]
let index = 0
for(let num of numbers) {
  index++;
  console.log(num);
}

for...of can be used in async functions:

The for…of loop also allows you to iterate over async data sources. Here’s an example:

async function load() {
  for await (const res of urls.map(fetch)) {
    const data = await res.json();
  }
}

Here, the loop iterates over an array of promises returned by urls.map(fetch). Once each of the promises resolves, it then iterates over the response object returned by the fetch call.

for…in loop

This is another variation of the for loop that can be used to iterate over the keys of an object:

const user = {id: 12,first_name: "John",last_name: "Doe",age: 27, gender: "male"}

for(const key in user) {
  console.log(key) // id first_name last_name age gender
  console.log(user[key]) // 12 John Doe 27 male
}

One thing to understand is that for...of iterates over values, whereas for...in iterates over keys. Under the hood, thefor...in loop calls Object.keys(object) .

A few things to note:

for…in iterates over inherited properties:

Object.prototype.foo = 123;
const obj = { bar: 456 };

for (const key in obj) {
  console.log(key); // 'bar', 'foo'
}

To avoid this, we can use hasOwnProperty to check if the property belongs to the object or someone in the prototype chain:

Object.prototype.foo = 123;
const obj = { bar: 456 };
for (const key in obj) {
  if(obj.hasOwnProperty(key)) {
    console.log(key); // 'bar'
  }
}

Don’t use for...in with arrays:

As mentioned earlier, for...in iterates over the keys of an object. Internally, arrays are objects with an index as the key. Therefore, when used with arrays, for...in will return the index of the values and not the actual values:

const nums = [101, 102];

for (const key in nums) {
  console.log(key); // '0', '1'
}

break and continue work as expected with for...in:

Using break exits the loop and continue skips to the next iteration. Both these keywords work as expected:

const user = {
  name: 'Alice',
  age: 30,
  role: 'admin',
  banned: false,
  gender: 'female' 
};

for (const key in user) {
  if (key === 'role') continue; // skip 'role'
  if (key === 'banned') break;  // stop before 'banned'

  console.log(key, user[key]); // name Alice age 30
}

do...while loop

The do…while loop isn’t common in real-world use cases. It is a post-tested loop, which means the loop runs at least once before the condition is evaluated to either true or false:

const nums = [1,2,3,4,5]
let i=0;
do {
console.log(nums[i]) // 1 2 3
i++
} while(i < 3)

Here are a few things to note about the do...while loop:

do...while runs at least once before the condition is evaluated:

const nums = [1,2,3,4,5]
let i=0;
do {
console.log(nums[i]) // 1
i++
} while(i < 0)

The loop will run once, even though the while condition is always going to be false.

The loop works with break and continue:

Similar to the other loops, with do...while, you can use break to exit the loop or continue to skip to the next iteration:

let i = 0;
do {
  i++;
  if (i === 2) continue;
  if (i === 4) break;
  console.log(i);   // 1 3
} while (i < 5);

while loop

A while loop runs as long as the condition evaluates to true or a true-like value. Unlike do..while, while doesn’t ensure the loop runs at least once. It’s also very easy to run into infinite loops, so you need to be very careful when writing one:

const nums = [1,2,3,4,5]
let i =0;
while(i < 6) {
  console.log(nums[i]) // 1 2 3 4 5
  i++;
}

Below are a few things to note about the while loop:

You can easily create infinite loops:

The code below creates an infinite loop, so you need to be extremely cautious when using while:

let i = 0;
while (i < 3) {
  console.log(i) // 0 0 0 0 0 0 .....
  // forgot i++;
}

while works with break and continue:

Like we saw for most other loops, while works with these statements:

let i = 0;
while (i < 5) {
  i++;
  if (i === 2) continue;
  if (i === 4) break;
  console.log(i);        // 1 3
}

forEach loop

forEach is a higher-order function that takes in a callback as its argument. This callback function is called for each element in the array. The callback function has the signature (element, index, array). Below is an example of how to use the forEach loop:

const nums = [1,2,3,4,5]

nums.forEach((num, index, nums) => {
  // do something
})

Things to note about this loop:

forEach is not ideal for async functions:

The forEach loop doesn’t wait for an async function to finish before going to the next element in the array. So, if you plan on async processing array elements one after the other, you should probably stick to the simple for loop:

const nums = [1,2,3,4,5]

nums.forEach(async (num, index, nums) => {
  const data = await fetch(`https://someAPI.com/${num}`) // control won't stop here
})

Keywords like continue and break don’t work as expected with forEach:

When using forEach, you can’t quit the loop early with break and skip an iteration with continue. Remember, forEach is a higher-order function:

const nums = [1, 2, 3]

nums.forEach((n) => {
  if (n === 2) return; // Only exits this iteration, not the loop
  console.log(n);
});

forEachalways runs to completion:

A forEach loop runs for all elements. You can skip processing for certain elements with a return statement:

const nums = [1, 2, 3]

nums.forEach((n) => {
  console.log(n); // 1 2 3
  return;
});

N.B., forEach also exists for other data structures like Set, Maps, and TypedArrays. They behave more or less in the same way.

map loop

map is generally used when we need to transform the values in an array and return the transformed values in an array. A common mistake is to use map to iterate over an array instead of a simple for loop or forEach function.

map is also a higher-order function that takes in a callback and returns an array of transformed values. Remember: to run side effects on elements in an array, use forEach, and to perform transformations on array elements and return the transformed array, use map:

const nums = [1, 2, 3];

const doubled = nums.map((num, index, nums) { return num * 2 } );

console.log(doubled); // [2, 4, 6]

Here are a few things one should keep in mind when using the map function on an array:

Always return from the map callback function:

The value returned by the callback function is returned as an element of an array from the map function, so never forget to return a value from the map callback function:

const nums = [1, 2, 3];

const result = nums.map(item => {
  return item * 2;
});

console.log(result) // [2,4,6]

map is not async-friendly:

Just like the forEach loop, map is a higher-order function. It doesn’t wait for any async operation to complete in the callback function:

const nums = [1, 2, 3];

const resultWithPromises = nums.map(num => {
  return fetch(`https://someapi.com/${num}`)
});

console.log(resultWithPromises) // [Promise, Promise, Promise]

const result = await Promise.all(nums.map(num => {
  return fetch(`https://someapi.com/${num}`)
}));

console.log(result) // 123 456 789

some loop

some is a lesser-known array function that checks if at least one element in an array passes the given test. You can think of this method as asking, “Do some item(s) in this array meet this condition?”

const nums = [1,2,3,4,5]

// check if atleast one number is even
const containsEven = nums.some((num, index, nums) => {
  return num%2 === 0 
})

console.log(containsEven) // true

Here, if at least one element (2 and 4) passes the test, the some function will return a truthy value. You would use some in real-world use cases like:

  • Checking if a user has a particular role (like an admin role)
  • Checking if a product is out of stock

A few things to note about the some function:

some always returns a Boolean:

some always returns a Boolean value. Looking at the function name, some might think that it would return an array of elements that pass the test. But instead, using some returns a Boolean value of true or false immediately after it encounters the appropriate number.

For example, if I have an array [1,2,3,4,5] and I pass a condition that checks if a number is even, as soon as 2 is encountered, the other numbers will be skipped and the function will “early return” with a true value. In this way, some can be thought of as a shortcut; it returns early if an element passes the test.

It doesn’t support await in callback:

Because some is a higher-order function, if there is an await in the callback, the callback won’t wait for the promise to resolve:

await arr.some(async x => await isValid(x)); // ❌ Doesn’t work

The callback returns a Boolean:

The callback passed to some should always return a Boolean. This Boolean signals if the element passed the test or not:

arr.some(item => item === 3);

every loop

every is an array method that checks if all elements in an array pass the test or not. It takes in a callback and expects it to return a Boolean value. The callback signature is similar to other array methods:

const nums = [2, 4, 6];
const allEven = nums.every(n => n % 2 === 0);
console.log(allEven); // true

The code above checks if all the elements in the array are even. Though rarely used, every can be used in the following use cases:

  • Checking if all users are active → users.every(u => u.isActive)
  • Validating items → items.every(i => validate(i))

A few things to note about every:

It short-circuits on the first false:

Because the function checks if all elements in the array pass the test, if any element fails the test (meaning it returns false), the function will short-circuit.

It’s not async-friendly:

Because every is a higher-order function, if there is an await in the callback, the callback won’t wait for the promise to resolve:

arr.every(async item => await isValid(item)); // ❌ Doesn't work as expected

TL;DR: Comparing the loops

We covered a lot of loops, and it might take time to digest this information. Here’s a quick overview of the loops we explored:

Feature for for…in for…of while do…while map() forEach() every() some()
Iterates over values ✅ (manual) ❌ (keys only) ✅ ✅ (manual) ✅ (manual) ✅ ✅ ✅ ✅
Target Indexable items Object keys Iterables Any condition Any condition Arrays Arrays Arrays Arrays
Returns a value ❌ ❌ ❌ ❌ ❌ ✅ New array ❌ undefined ✅ Boolean ✅ Boolean
Can break/continue ✅ ✅ ✅ ✅ ✅ ❌ ❌ ❌ ❌
Executes at least once ❌ ❌ ❌ ❌ ✅ ❌ ❌ ❌ ❌
Supports await ✅ (manually) ❌ ✅ (for await) ✅ (manually) ✅ (manually) ❌ ❌ ❌ ❌

JavaScript loops are some of the most commonly used constructs. Knowing the difference between each of them will help you write cleaner and more performant code that enhances readability.

Thanks for reading!

The post JavaScript loops explained, and best practices appeared first on LogRocket Blog.

 

This post first appeared on Read More