You're staring at a screen, probably at 2 AM, wondering why your array values are suddenly undefined or why your loop is running through keys you never even added. It happens. Honestly, using a c for i in array style loop—specifically the for...in statement—is one of those "rite of passage" mistakes in JavaScript. We’ve all been there. You want to iterate. You see "in." You think, "Hey, that sounds like it should work."
But it doesn't. At least, not the way you think it does.
If you’re coming from Python, you’re used to for i in list giving you the actual items. In JavaScript, for...in gives you the keys (the indices). It’s a subtle distinction that breaks production code daily. It’s not just about getting the index instead of the value; it’s about how JavaScript treats objects under the hood.
Why the for...in loop hates your arrays
Let’s be real: for...in was never actually meant for arrays. It was designed for objects. In JavaScript, arrays are technically objects, which is where the confusion starts. When you use a c for i in array approach with for...in, you aren't just iterating over the numbers 0, 1, and 2. You are iterating over all enumerable properties of that object.
Think about that for a second. If some library you’re using decided to mess with the Array.prototype (which, thankfully, most don't do anymore, but it's possible), your for...in loop will happily grab those extra methods and try to process them as if they were part of your data.
// This is the danger zone
const fruits = ['apple', 'banana', 'cherry'];
Array.prototype.weirdMethod = function() { return 'uh oh'; };
for (let i in fruits) {
console.log(i); // This might log "0", "1", "2", and then... "weirdMethod"
}
It’s messy. It’s slow. And frankly, it’s unpredictable. If you have a "sparse" array—like const arr = []; arr[100] = 'last';—the for...in loop will skip the empty slots, but it still treats the index as a string ("0", "1", etc.) rather than a number. This leads to weird bugs when you try to do math like i + 1 and end up with "01" instead of 1.
The for...of alternative you actually want
If you want the values, just use for...of. It was introduced in ES6 specifically to solve this headache. It’s cleaner. It’s faster. It actually respects the order of the array.
👉 See also: Converting TIFF to JPEG: Why Your Photos Look Blurry and How to Fix It
When you write for (const value of array), JavaScript calls the array's internal iterator. It doesn't care about extra properties tacked onto the prototype. It just gives you the data.
But what if you actually need the index? You’ve got options. You could use the old-school for (let i = 0; i < arr.length; i++). It’s verbose, sure, but it’s still the fastest way to loop in many engines like V8. Or, if you want that modern feel, use .entries().
Breaking down the .entries() method
This is kinky, but in a good way. for (const [i, val] of array.entries()) gives you both the index and the value in a neat little package. It uses destructuring. It’s readable. It keeps the index as a number. Most importantly, it doesn’t break when some random script injects a property into the global Array object.
Performance: Does it actually matter?
People love to argue about loop performance. "Oh, forEach is 10% slower than a manual for-loop!" Honestly? For 99% of web apps, it doesn't matter. If you’re looping through 50 items to show a list of cat names, the browser doesn't care.
However, if you are doing heavy data processing—maybe you're building a custom physics engine or a massive data visualization—then the c for i in array choice becomes critical. In these cases, for...in is demonstrably the slowest because it has to check the prototype chain at every step.
According to benchmarks frequently cited by developers on platforms like Stack Overflow and MDN, a standard for loop or even a for...of loop will outperform for...in by a significant margin. If you’re building something for the 2026 web where performance and Core Web Vitals are king, don't leave performance on the table for a loop that wasn't even built for the task.
Common pitfalls with forEach
You might think array.forEach() is the savior here. It’s functional! It’s cool! But it has a massive flaw: you can’t break or continue.
📖 Related: How to Remove Books Off Kindle Fire Without Losing Your Mind
If you’re inside a forEach and you find the item you’re looking for, you’re stuck. The loop will keep going until it hits the end of the array, wasting cycles. If you need control flow, go back to for...of. It handles break, continue, and yield perfectly. It’s just more flexible.
The "Array-like" Object Trap
Sometimes you aren't even working with a real array. Think about NodeList (from document.querySelectorAll) or the arguments object. These are "array-like." They have a length and indexed elements, but they don't have all the array methods like .map() or .filter().
In the old days, we had to do Array.prototype.slice.call(nodeList).forEach(...). It was gross. Nowadays, for...of works on anything "iterable." This includes strings, Sets, and Maps. This is why the c for i in array mindset needs to shift toward the c for value of iterable mindset.
Real-world debugging scenario
Imagine you’re building a shopping cart. You have an array of prices. You use for...in to sum them up.
let total = 0;
const prices = [10, 20, 30];
for (let i in prices) {
total += i;
}
You expect 60. You get "0012". Why? Because i is the string index "0", "1", and "2". JavaScript sees the + and thinks you want to concatenate strings. This is a classic "JavaScript is weird" moment that is entirely preventable by using the right loop.
Actionable Insights for Clean Loops
Stop using for...in for arrays. Just stop. It’s for objects when you need to peek at keys.
If you need values, use for...of. It’s the gold standard for readability.
📖 Related: How Much Does an iPhone S Cost: The Real Prices Nobody Tells You
If you need the index, use a standard for loop or array.entries().
When working with asynchronous code (like async/await inside a loop), for...of is your best friend. forEach will not wait for your promises to resolve, leading to a mess of unhandled async operations. for...of respects the await keyword, pausing the loop until the promise settles.
Next time you start typing for (let i in..., catch yourself. Ask if you're looking at an object or an array. If it's an array, hit backspace and switch to of. Your future self, debugging at 2 AM, will thank you.
Check your current codebase for any instance of for...in used on an array and replace it with for...of. If you see performance lagging in data-heavy sections, revert to a traditional for loop with a cached length variable. This simple refactor can eliminate hidden bugs and slightly boost your execution speed without adding any complexity.