DEV Community

Cover image for JavaScript loops explained, and best practices
Megan Lee for LogRocket

Posted on • Originally published at blog.logrocket.com

JavaScript loops explained, and best practices

Written by Rahul Padalkar✏️

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. 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 } 
Enter fullscreen mode Exit fullscreen mode

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]); } 
Enter fullscreen mode Exit fullscreen mode

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 `continue`keyword in a `for` loop skips to the next element:** ```javascript >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: ```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 `forof` loop: ```javascript const numbers = [1,2,3,4,5] for(const num of numbers) { console.log(num) // 1 2 3 4 5 } 
Enter fullscreen mode Exit fullscreen mode

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``` {% endraw %} **{% raw %}`for...of`{% endraw %} can't access the index:** Unlike the {% raw %}`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: ```javascript const numbers = [1,2,3,4] let index = 0 for(let num of numbers) { index++; console.log(num); } 
Enter fullscreen mode Exit fullscreen mode

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(); } } 
Enter fullscreen mode Exit fullscreen mode

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 } 
Enter fullscreen mode Exit fullscreen mode

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: ```javascript Object.prototype.foo = 123; const obj = { bar: 456 }; for (const key in obj) { if(obj.hasOwnProperty(key)) { console.log(key); // 'bar' } } 
Enter fullscreen mode Exit fullscreen mode

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' } 
Enter fullscreen mode Exit fullscreen mode

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 } 
Enter fullscreen mode Exit fullscreen mode

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) 
Enter fullscreen mode Exit fullscreen mode

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: ```javascript let i = 0; do { i++; if (i === 2) continue; if (i === 4) break; console.log(i); // 1 3 } while (i < 5); 
Enter fullscreen mode Exit fullscreen mode

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++; } 
Enter fullscreen mode Exit fullscreen mode

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++; } 
Enter fullscreen mode Exit fullscreen mode

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 } 
Enter fullscreen mode Exit fullscreen mode

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 }) 
Enter fullscreen mode Exit fullscreen mode

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 }) 
Enter fullscreen mode Exit fullscreen mode

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); }); 
Enter fullscreen mode Exit fullscreen mode

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; }); 
Enter fullscreen mode Exit fullscreen mode

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] 
Enter fullscreen mode Exit fullscreen mode

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] 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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); 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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!


LogRocket: Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.

LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

Try it for free.

Top comments (0)