for..in vs for..of vs forEach() in JavaScript
JavaScript array methods inplay
November 1, 2024updated Nov 1, 2024

JavaScript offers several ways to iterate over data structures, and choosing the right one can significantly impact your code's readability and performance. In this post, we will explore three common iteration methods and demonstrate how to choose the most suitable one for your algorithm.
Let’s start with some simple examples that highlight their differences:
// Using for...in (not recommended for arrays!)
const colors = ['red', 'blue', 'green'];
const keys = [];
for (const index in colors) {
keys.push(index);
}
console.log(keys);
// Result: ['0', '1', '2']
// Note: Returns the index of each element in the colors array// Using for...of
const colors = ['red', 'blue', 'green'];
const elements = [];
for (const color of colors) {
elements.push(color);
}
console.log(elements);
// Result: ['red', 'blue', 'green']
// Note: The for...of loop iterates over the actual elements of the array// Using forEach
const colors = ['red', 'blue', 'green'];
const elements = [];
colors.forEach(color => {
elements.push(color);
});
console.log(elements);
// Result: ['red', 'blue', 'green']
// Note: forEach also iterates over the actual elements of the arrayA Brief Overview of Iteration in JavaScript
At its core, iteration in JavaScript is about accessing elements in a sequence, one at a time. Whether you're working with arrays, strings, objects, or other data structures, JavaScript provides various mechanisms to traverse data efficiently.
The Evolution of Iteration in JavaScript
JavaScript's iteration capabilities have evolved significantly over time:
// Traditional for loop (Old School)
for (let i = 0; i < array.length; i++) {
// Requires managing an index counter
}
// for...in loop (ES1)
for (const key in object) {
// Designed for objects but can be used with arrays (not recommended)
// Iterates over enumerable properties
}
// forEach method (ES5)
array.forEach(element => {
// Introduced with ES5
// Provides a declarative approach
});
// for...of loop (ES6)
for (const element of array) {
// Modern and clean syntax
// Works with any iterable object
}Why Understanding Loop Differences Matters
Understanding the differences between various iterators is crucial for avoiding bugs and writing better code. For example, knowing that for...in loops iterate over object keys while for...of and forEach iterate over array elements helps you select the most appropriate method for your use case. Let’s delve deeper into each of these iterators.
The for...in Loop
The for...in loop is designed to iterate over object keys, not array elements. While it can be used with arrays, it often produces unexpected results due to JavaScript's internal handling of arrays as special objects.
Arrays are technically objects in JavaScript, with numeric keys and a length property:
const colors = ['red', 'blue', 'green'];
// Checking the type of the colors array
console.log(typeof colors); // 'object'
// Using Object.keys to get array indices
console.log(Object.keys(colors));
// Result: ['0', '1', '2']Using a standard object highlights how for...in works:
// Using for...in with an object
const obj = {
firstname: "Kennedy",
surname: "Anyidoho",
};
const keys = [];
for (const key in obj) {
keys.push(key);
}
console.log(keys);
// Result: ['firstname', 'surname']When using the for...in loop to iterate over an array, it produces similar results, as JavaScript assigns each array element a key equivalent to its index. Thus under the hood, JavaScript treats arrays as an object with keys.
// Using for...in with an array
const colors = ['red', 'blue', 'green'];
const keys = [];
for (const key in colors) {
keys.push(key);
}
console.log(keys);
// Result: ['0', '1', '2']Why is it not advisable to use the for...in loop with arrays?
JavaScript arrays can be sparse (i.e., they can have holes or missing elements). For example, we can declare an empty array and insert values at specific indexes:
const someArray = [];
someArray[4] = 'true';
someArray[3] = 'false';
console.log(someArray);
// Result: [ <3 empty items>, 'false', 'true' ]Now let’s use the for...in loop to iterate over the same array and see what happens:
const keys = [];
// Assuming we are using the same array from the previous code block
for (const key in someArray) {
keys.push(key);
}
console.log(keys);
// Result: ['3','4']You'll notice that only the indexes that have values are returned. This happens because JavaScript, unlike other programming languages like Ruby, does not automatically fill empty indexes with null, undefined, or throw an error like Python does. Instead, JavaScript simply skips over these indexes when iterating over the array.
To access the actual elements, we have to explicitly reference them using their indexes:
const keys = [];
// Assuming we are using the same array from the previous code block
for (const key in someArray) {
keys.push(someArray[key]);
}
console.log(keys);
// Result: ['false', 'true']While for...in can technically work with arrays, it’s not the most efficient or intuitive method. You have to do extra work (i.e., accessing values by index), and it can lead to bugs if you’re not careful about potential prototype properties.
Instead, for array iteration, loops like for...of or array methods like forEach() are much more straightforward. These methods give you direct access to the values without needing to access them via the index, making the code cleaner and easier to understand.
The for...of Loop
Fortunately, JavaScript provides another iterator known as the for...of loop, which directly returns the values in the array, instead of the keys, like in the case of the for...in loop.
const someArray = [];
someArray[4] = 'true';
someArray[3] = 'false';
const values = [];
for (const value of someArray) {
values.push(value);
}
console.log(values);
// Result: ['false', 'true']-
Unlike
for...in, which iterates over the indexes (keys),for...ofdirectly accesses the values at those indexes. This makes it much easier to work with arrays, especially when dealing with sparse arrays or when you just need the values, not the indexes. -
The output will be
['false', 'true'], which are the values at indexes3and4. Notice how we don't need to deal with empty indexes, and the values are directly returned.
Why for...of is Preferred for Arrays
The for...of loop is more intuitive and cleaner for iterating over arrays, especially when the goal is to process the values. It eliminates the need for additional code to access values via their indexes, and it's more straightforward to read.
If you are working with arrays and only need to process the values, the for...of loop is the way to go. It reduces the chance of errors, avoids unnecessary code, and makes your intentions clear. For sparse arrays, this is especially important, as it skips over any gaps automatically.
The forEach() array method
The forEach() method just like the for...of loop gives us each element in the array. Then also gives as the opportunity to pass a callback function to it, which is called once for each element in the array.
const someArray = [1, 2, 3];
sum = 0;
numbers.forEach(n=>{
sum += n;
})
console.log(sum)
//result: 6Another interesting thing is that just like the for...in loop, the forEach() loop's callback function gives us an option to get the index (which in the case of arrays we can also refer to as the key) of the element like so:
const someArray = [1, 2, 3]
const arrayWithIndexes = [];
someArray.forEach((element, index) => {
arrayWithIndexes.push(`${element} is at index ${index}`);
});
console.log(arrayWithIndexes);
// Result: ['1 is at index 0', '2 is at index 1', '3 is at index 2']The callback function can also be defined outside the loop like so:
const callBack = (element, index) => {
arrayWithIndexes.push(`${element} is at index ${index}`);
}
const someArray = [1, 2, 3]
const arrayWithIndexes = [];
someArray.forEach(callBack);
console.log(arrayWithIndexes);
// Result: ['1 is at index 0', '2 is at index 1', '3 is at index 2']Caution ⚠️
When dealing with return values in a forEach loop, extra caution has to be taken. Let us use the following code examples to demonstrate this:
we write a simple function to check if an array of numbers contains one or more even numbers
- returning values in
for...ofloops works normal
const isEven = (...array) =>{
for(const n of array){
if(n % 2 === 0) return true;
}
return false;
}
console.log(isEven(2, 4, 5, 6))
//result: trueFrom the code above, the function isEven returns true because the first even number it encounters is 2 and it returns true immediately. This is the expected behavior. In a case where there are no even numbers in the array, the function returns false as expected.
This is because the return false statement is outside the loop and only gets executed if the loop completes without returning true. We cannot say the same for the forEach() loop. Let us see why:
- returning values in
forEachloops does not work as expected
const isEven = (...array) =>{
array.forEach(n=>{
if(n % 2 === 0) return true;
})
return false;
}
console.log(isEven([2, 4, 5, 6]))
//result: falseFrom the code above, the function isEven returns false even though there is an even number in the array. This is because the return true statement inside the forEach loop does not return from the isEven function but from the forEach loop. The return true statement only returns from the callback function passed to the forEach loop.
This is because on every iteration of the loop, the return values get discarded and when the loop in done executing, control is transfered to the next statement outside of the loop, hence the return false statement eventually gets executed.
How do we solve this problem? we can use a slightly different approach where we use a varaiable to keep track of the truthy or falsy state like so:
const isEven = (...array) =>{
let state = false;
array.forEach(n=>{
if(n % 2 === 0) state = true;
})
return state;
}
console.log(isEven([2, 4, 5, 6]))
//result: trueThis way we avoid that edge case with the forEach() loop.
Conclusion
JavaScript iterators are very powerful but can lead to serious consequences if you do not understand and use them the right way. Understanding them very well can come in handy in selecting the right iterator for your usecase.
Happy coding :)