Use for...of: for (const item of items) { … }. It gives you each element, and break and continue work. Need the index too? Loop over items.entries() and destructure it: for (const [i, item] of items.entries()).
JavaScript has five ways to loop through an array, and they are not interchangeable. for...of is the modern default. forEach() is a method that takes a callback and cannot be stopped. The classic for (let i = 0; …) loop is still the right tool for walking backwards or in steps. for...in looks similar but was built for object keys, and it is a trap on arrays. This page covers all of them, how to get the index from each, and how to stop early when forEach won't let you. Each example runs on this page: hit Run, then edit the code and run it again.
1for...of (and entries() for the index)Recommended
for...of (ES2015) walks the values of anything iterable: arrays, strings, Maps, Sets. Declare the loop variable with const unless you reassign it inside the body. There is no index by default. When you need one, entries() yields [index, value] pairs, and array destructuring unpacks each pair into two names.
Output
Prints JavaScript, Python, Go, then 0 JavaScript, 1 Python, 2 Go, and finally kept: JavaScript: continue skipped Python and break stopped the loop at Go. entries() returns an iterator, not an array, so it doesn't copy the list. For const a = ["x", "y"], logging a.entries() shows Object [Array Iterator] {}, and spreading it with [...a.entries()] gives [ [ 0, 'x' ], [ 1, 'y' ] ]. Inside an async function, for...of is also the loop that works with await: each iteration waits for the previous one to finish.
2forEach() with the index argument
forEach() calls your function once per element and passes it three arguments: the value, the index and the array itself. Take only the ones you need. It reads well at the end of a chain of array methods, but it is a function call rather than a loop, which changes two things: return ends only the current callback, so it behaves like continue, and forEach itself always returns undefined.
Output
Prints 1. $4.50, 2. $12.00, 3. $7.25, then under 10: 4.5 and under 10: 7.25 (the return skipped 12), then undefined. That last line is the classic mistake: if you are building a new array, you wanted map() (or filter()), which returns one. Two more limits: forEach does not wait for async callbacks, so an await inside it doesn't pause the loop, and there is no way to stop it early. The next section covers what to use instead.
3Stopping early: break, find() and some()
You cannot break out of forEach. The keyword is only legal inside a loop, and a callback isn't one, so writing it there is a parse error: SyntaxError: Illegal break statement. You have two good options. Use for...of and break. Or use the array method that already describes the search: find() and findIndex() stop at the first match, some() stops at the first true, and every() stops at the first false.
Output
Prints first reading over 25 is at index 2, then 30, 2, true (4 is under 5) and false (4 is not over 5). The counters show the difference: some() checked 3 of 5 but forEach visited 5 of 5. find() returns undefined and findIndex() returns -1 when nothing matches. Old answers suggest throwing an exception inside forEach and catching it outside. That does stop the loop, but it uses an error for ordinary control flow. Use for...of instead.
4The classic for loop: backwards, steps and writing back
for (let i = 0; i < items.length; i++) is the C-style loop, and it predates everything above. For a plain forward walk, for...of is shorter and can't go off by one. The index loop is still the right answer when the index doesn't just count 0, 1, 2: walking backwards, stepping by two, or assigning items[i] in place. Use let, not var, so i belongs to the loop and each closure created inside it gets its own copy.
Output
The first loop prints 0 2 through 4 11. The backwards walk collects [ 11, 7, 5, 3, 2 ], the stepped one [ 2, 5, 11 ], and the last loop leaves [ 20, 30, 50, 70, 110 ]. Changing the const array's elements is allowed, because const only stops the name from being reassigned. You will also see old code that caches items.length in a variable before the loop. Modern engines make that unnecessary, and reading .length on each pass means the loop notices if the array shrinks.
5Why for...in is a bad idea for arrays
for...in enumerates an object's property keys. Arrays are objects whose indexes happen to be keys, so it seems to work, but it has three problems. The keys are strings, so i + 1 concatenates. It visits every enumerable property, including ones someone added to the array and inherited ones (a library that patches Array.prototype leaks into every loop). And it skips holes in sparse arrays. Keep it for plain objects.
Output
The first loop prints 0 string, 1 string, 2 string and note string. The extra property shows up as if it were an element. The second loop prints 01, 11, 21, not 1, 2, 3. for...of prints just red, green, blue. The last line is [ '0', '2' ] [ 0, 2 ] [ 1, undefined, 3 ]: for...in and forEach skip the hole at index 1, while for...of visits it as undefined. Looping over an object instead? Use Object.entries(obj) with for...of.
6Which should you use?
| Method | Index | Can stop early | Best for |
|---|---|---|---|
| for (const x of arr) | No | Yes, break | The default; works with await |
| for (const [i, x] of arr.entries()) | Yes | Yes, break | Index + value in a real loop |
| arr.forEach((x, i) => …) | Yes | No | Side effects on every item, end of a chain |
| find() / some() / every() | findIndex() | Yes, automatically | Searching and yes/no checks |
| for (let i = 0; i < arr.length; i++) | Yes | Yes, break | Backwards, custom steps, writing arr[i] |
| for (const i in arr) | As a string | Yes, break | Not arrays: plain object keys |
Frequently asked questions
What is the best way to loop through an array in JavaScript?
for (const item of items). It reads cleanly, visits only the elements, supports break and continue, and works with await inside an async function. Use items.entries() when you also need the index, and a classic for (let i = 0; ...) loop only when you need to walk backwards, step, or assign items[i].
How do I get the index in a for...of loop?
Loop over entries() and destructure: for (const [i, item] of items.entries()). entries() yields [index, value] pairs as an iterator, so nothing is copied. With forEach, the index is the second callback argument: items.forEach((item, i) => ...).
How do I break out of a forEach loop?
You can’t. break inside the callback is SyntaxError: Illegal break statement, and return only ends the current call, so the loop still visits every item. Switch to for...of and use break, or use a method that stops by itself: find(), findIndex(), some() or every(). Throwing an exception out of forEach also works, but it uses errors for control flow.
Why is using for...in for array iteration a bad idea?
for...in enumerates property keys, not elements. The keys are strings (so i + 1 gives "01"), it includes any extra or inherited enumerable properties such as something added to Array.prototype, and it skips holes in sparse arrays. Use for...of for arrays and keep for...in for plain objects.
Is forEach slower than a for loop?
A classic for loop avoids a function call per element, so it can win in micro-benchmarks, but modern engines optimise all three forms well and the difference rarely matters next to the work inside the loop. Choose by behaviour: for...of when you might need break or await, forEach for simple side effects, and the index loop for backwards or stepped walks.
Can I use await inside forEach?
Not usefully. forEach calls your async callback and ignores the promise it returns, so it doesn’t wait, and the code after the forEach runs before the callbacks finish. For one-at-a-time processing, use for (const item of items) { await work(item); } inside an async function (top-level await works in ES modules). To run them all at once, use await Promise.all(items.map(work)).
Run it yourself
Open any of these in the full JavaScript editor to tweak, run and share them.
JavaScript playground