Tested on: Node.js v20.18.2. A short note after each runnable snippet describes what you should see in the console.
People often mix up JavaScript each vs in because forEach and for...in both sound like “loop over something,” yet they answer different questions: values with an array API versus enumerable property names (often keys on objects, or string indices on arrays). This guide walks from basic loops to foreach in JavaScript-style APIs, then compares javascript foreach vs for-style control flow, with short expected-output notes after each snippet.
For a compact field guide (including Object.hasOwn and sparse-array notes), start with JavaScript forEach vs for...in and return here for the longer narrative.
forEach vs for...in vs for...of (quick map)
| Construct | Typical target | You get | Easy break / continue? |
|---|---|---|---|
array.forEach(fn) |
Arrays (and some other typed collections) | Elements via callback (value, index, arr) |
No (use some/find/for...of if you must stop early) |
for (const k in obj) |
Objects (and arrays as objects) | Enumerable string keys k (may include extras on arrays) |
Yes |
for (const v of iterable) |
Iterables (Array, Map, Set, String, …) | Values from the iterator protocol | Yes |
Official framing: MDN contrasts for...of and for...in as values vs enumerable keys. Array.prototype.forEach is a method, not a statement.
Primitives vs objects (why “in” targets keys)
Modern JavaScript distinguishes primitive values (e.g. string, number, boolean, bigint, symbol) from objects (including plain objects, arrays, functions). Arrays are objects with numeric string keys ("0", "1", …) and a special length. That is why a key loop and a value loop behave differently on the same array.
const literal = { name: "Bob", meanGrade: 79.47, codes: true };
const arr = ["Bob", 79.47, true];
console.log(typeof literal, Array.isArray(arr));You should see one line logging object true.
Classic loops (while, do-while, for)
while
const array = ["Bob", 79.47, true];
let count = 0;
while (count < array.length) {
console.log(array[count]);
count++;
}You should see 3 lines, in order: Bob, 79.47, true.
do...while
The body runs at least once, even if the condition is false afterward.
const array = [];
let count = 1;
do {
console.log("In the loop");
count++;
} while (count < array.length);You should see one line logging In the loop.
for
const array = ["Bob", 79.47, true];
for (let count = 0; count < array.length; count++) {
console.log(array[count]);
}You should see 3 lines, in order: Bob, 79.47, true.
These index loops are still useful when you need break, continue, or a custom step.
for...of (values from an iterable)
for...of is usually what you want for “foreach” in JavaScript over array values when you are not chaining a collection method.
const array = ["Bob", 79.47, true];
for (const value of array) {
console.log(value);
}You should see 3 lines, in order: Bob, 79.47, true.
for...in (enumerable keys on an object)
for...in walks enumerable string keys on an object (own and inherited unless you filter). On arrays, keys are indices like "0", "1", … plus any enumerable properties you attach to the array object.
console.log("...Object...");
const literal = { name: "Bob", meanGrade: 79.47, codes: true };
for (const key in literal) {
console.log(literal[key]);
}
console.log("\n...Array...");
const array = ["Lorem", 89.67, false];
for (const key in array) {
console.log(array[key]);
}You should see 8 lines, in order: ...Object..., Bob, 79.47, true, ...Array..., Lorem, 89.67, false.
Higher-order methods and forEach
A higher-order function accepts another function. Array.prototype.forEach is the built-in “run this callback for each element” API—handy for side effects when you already have an array.
const array = ["Bob", 79.47, true];
array.forEach((element) => {
console.log(element);
});You should see 3 lines, in order: Bob, 79.47, true.
The callback is just a function value—here is the same idea with a named function you could pass to forEach:
const callbackFunction = (input) => {
console.log(input);
};
callbackFunction("from callback");You should see one line logging from callback.
Side-by-side: forEach vs for...in on a dense array
On a normal dense array, both walk the same underlying element order: forEach visits values via the method API, while for...in visits index keys and you read array[index] yourself.
const array = ["Bob", 79.47, true];
console.log("forEach:");
array.forEach((element) => console.log(element));
console.log("for...in:");
for (const index in array) {
console.log(array[index]);
}You should see 8 lines, in order: forEach:, Bob, 79.47, true, for...in:, Bob, 79.47, true.
Pitfall 1 — for...in sees inherited enumerable keys
Use Object.hasOwn(obj, key) when you only want an object’s own keys (still includes custom own properties like arr.label, but skips inherited enumerables):
const parent = { inherited: 99 };
const child = Object.create(parent);
child.own = "a";
console.log("for...in (shows inherited enumerable):");
for (const k in child) {
console.log(k, child[k]);
}
console.log("Object.hasOwn filter:");
for (const k in child) {
if (Object.hasOwn(child, k)) {
console.log(k, child[k]);
}
}You should see 5 lines, in order: for...in (shows inherited enumerable):, own a, inherited 99, Object.hasOwn filter:, own a.
Pitfall 2 — extra properties on an array object
Because arrays are objects, for...in can surface non-index enumerable properties you added:
const arr = [10, 20];
arr.label = "nums";
for (const k in arr) {
console.log(k, arr[k]);
}You should see 3 lines, in order: 0 10, 1 20, label nums.
For “values only,” prefer for...of or forEach.
Pitfall 3 — sparse arrays (foreach js / forin javascript behavior)
const sparse = ["a", , "c"];
console.log("forEach:");
sparse.forEach((x, i) => console.log(i, x));
console.log("for...in:");
for (const k in sparse) {
console.log(k, sparse[k]);
}
console.log("for...of:");
for (const v of sparse) {
console.log(v);
}You should see 10 lines, in order: forEach:, 0 a, 2 c, for...in:, 0 a, 2 c, for...of:, a, undefined, c.
forEach and for...in skip the missing index; for...of still yields undefined for the hole because it walks the array length.
Practical pattern — user list (mock API data, no network)
Older tutorials used fetch plus screenshots; here is the same lesson with mock data in the shape of the public reqres users payload—so the output is reproducible offline. On a dense array, forEach and for...in print the same avatar URLs in the same order (because for...in walks indices 0…n-1 in order).
const users = [
{ id: 1, avatar: "https://reqres.in/img/faces/1-image.jpg" },
{ id: 4, avatar: "https://reqres.in/img/faces/4-image.jpg" },
{ id: 2, avatar: "https://reqres.in/img/faces/2-image.jpg" },
{ id: 6, avatar: "https://reqres.in/img/faces/6-image.jpg" },
{ id: 3, avatar: "https://reqres.in/img/faces/3-image.jpg" },
{ id: 5, avatar: "https://reqres.in/img/faces/5-image.jpg" },
];
console.log("--- forEach (element order) ---");
users.forEach((u) => console.log(u.avatar));
console.log("--- for...in (index order on dense array) ---");
for (const i in users) {
console.log(users[i].avatar);
}You should see 14 lines of console output in print order, starting with --- forEach (element order) ---.
If you mix in fs.appendFile (async) inside forEach, disk order can race even when the in-memory array order is clear—use appendFileSync, for...of with await, or Promise.all when file ordering must match visitation order.
JavaScript for vs forEach — control flow and short-circuiting
forEach cannot break out of the outer loop; return inside the callback only exits that invocation. To stop early on arrays, use a for / for...of loop, or a method like some:
console.log(
[1, 2, 3, 4].some((n) => {
console.log("visit", n);
return n === 2;
}),
);You should see 3 lines, in order: visit 1, visit 2, true.
Takeaways
Default to for...of for values, use forEach for side effects without early exit, and reserve for...in for object keys (with Object.hasOwn when inheritance matters).
- Treat
for...inas a key enumerator for objects (withObject.hasOwnwhen you mean “own keys only”), not your default array value iterator. - Treat
forEachas a readable side-effect tool on arrays when you do not needbreak/continuein the surrounding control flow. - Prefer
for...ofwhen you want values with full loop semantics (includingawaitin async functions in modern engines).
References
- MDN:
for...of - MDN:
for...in - MDN:
Array.prototype.forEach - Node.js
fswrite patterns if you are persisting loop output to disk

