JavaScript For In For Of
JavaScript gives you two loop constructs that look almost identical but do very different jobs: for...in walks over the enumerable property names (keys) of an object, while for...of walks over the values produced by an iterable. Confusing the two is one of the most common sources of subtle bugs for people coming from other languages, especially when looping over arrays. This lesson explains exactly what each loop visits, how the mechanism works internally, and when to reach for one over the other.
Overview: How for…in and for…of Work
for...in is a property enumerator. Given an object, it produces the names (as strings) of that object’s enumerable properties, one per iteration — but not just its own properties. It also walks up the prototype chain and includes any enumerable properties inherited from prototypes. This is why looping over an array with for...in can surprise you: an array’s numeric indexes (“0”, “1”, “2”…) are enumerable own properties, so they show up, but so would any enumerable property someone accidentally added to Array.prototype.
for...of is completely different: it does not care about property names or enumerability at all. It works with the iterable protocol. An object is iterable if it has a method keyed by the well-known symbol Symbol.iterator that returns an iterator — an object with a next() method that returns { value, done } pairs. Arrays, strings, Map, Set, NodeList, the arguments object, and generator objects are all iterable out of the box. Plain object literals ({}) are not iterable by default, which is the single most common reason beginners see “is not iterable” errors.
So the mental model is simple: use for...in when you care about an object’s keys, and use for...of when you care about a collection’s values.
Syntax
for (const key in object) {
// key is always a string, even for array indexes
console.log(key, object[key]);
}
for (const value of iterable) {
// value is whatever the iterator yields
console.log(value);
}
| Aspect | for…in | for…of |
|---|---|---|
| Iterates over | Enumerable property keys (own + inherited) | Values produced by an iterator |
| Works on | Any object | Only iterables (Array, String, Map, Set, etc.) |
| Loop variable type | Always a string | Whatever type the iterable yields |
| Order | Integer-like keys first (ascending), then insertion order for the rest | Defined by the iterator (arrays & strings: index order) |
| Typical use | Inspecting plain object properties | Looping over arrays, strings, Maps, Sets, generators |
Examples
Example 1: for…of over an array
const colors = ['red', 'green', 'blue'];
for (const color of colors) {
console.log(color);
}
Output:
red
green
blue
Each pass of the loop asks the array’s iterator for the next value and binds it directly to color. There is no index bookkeeping to write yourself, and the values come out in the array’s natural order.
Example 2: for…in over a plain object
const car = {
make: 'Toyota',
model: 'Corolla',
year: 2022
};
for (const key in car) {
console.log(`${key}: ${car[key]}`);
}
Output:
make: Toyota
model: Corolla
year: 2022
Here key is a string on every pass (“make”, “model”, “year”), and you look the value up yourself with car[key]. This is exactly the scenario for...in is designed for: enumerating the properties of a plain object.
Example 3: for…of with Map entries and array entries()
const inventory = new Map([
['apples', 5],
['bananas', 12],
['cherries', 30]
]);
for (const [item, quantity] of inventory) {
console.log(`${item}: ${quantity}`);
}
const fruits = ['apple', 'banana', 'cherry'];
for (const [index, fruit] of fruits.entries()) {
console.log(`${index} -> ${fruit}`);
}
Output:
apples: 5
bananas: 12
cherries: 30
0 -> apple
1 -> banana
2 -> cherry
A Map iterates as [key, value] pairs by default, so destructuring them directly in the loop header is idiomatic. Arrays don’t hand you an index automatically with for...of, but calling .entries() gives you an iterable of [index, value] pairs, which is the standard way to get both when you need one.
Under the Hood: The Two Protocols
For for...of, the engine performs, conceptually, these steps for for (const v of iterable) { body }:
- Call
iterable[Symbol.iterator]()to obtain an iterator object. - Call
iterator.next(), which returns{ value, done }. - If
doneistrue, stop looping. - Otherwise bind
valuetov, runbody, then go back to step 2. - If the loop exits early — via
break,return, or a thrown error — the engine callsiterator.return()if that method exists, giving the iterable a chance to clean up (close a file handle, release a lock, etc.).
For for...in, there is no iterator object at all. The engine builds a list of enumerable property keys by inspecting the object itself and then walking up its prototype chain, skipping any key that has already been seen closer to the object (so shadowed inherited properties aren’t listed twice). Per the specification, integer-like keys are visited first in ascending numeric order, followed by string keys in the order they were created, followed by inherited enumerable keys in the same fashion. Because this list depends on the entire prototype chain, adding an enumerable property to a built-in prototype (like Array.prototype) affects every for...in loop over every array in your program — not just the one you meant to change.
Common Mistakes
Mistake 1: Using for…in on arrays
Array.prototype.extra = 'oops';
const nums = [10, 20, 30];
for (const key in nums) {
console.log(key, nums[key]);
}
delete Array.prototype.extra;
Output:
0 10
1 20
2 30
extra oops
Adding any enumerable property to Array.prototype (something a careless library or polyfill might do) makes it show up in every for...in loop over every array, mixed in with the real indexes. The fix is to never use for...in on arrays: use for...of, or filter to own properties only:
Array.prototype.extra = 'oops';
const nums = [10, 20, 30];
for (const key in nums) {
if (Object.hasOwn(nums, key)) {
console.log(key, nums[key]);
}
}
delete Array.prototype.extra;
Output:
0 10
1 20
2 30
Object.hasOwn(nums, key) filters out anything inherited from the prototype chain, leaving only the array’s own indexes.
Mistake 2: Using for…of on a plain object
const person = { name: 'Ada', age: 36 };
try {
for (const value of person) {
console.log(value);
}
} catch (error) {
console.log(error.message);
}
Output:
person is not iterable
Plain objects don’t implement Symbol.iterator, so for...of throws a TypeError immediately. If you want to loop over an object’s values (or keys, or both) with for...of, convert it to an iterable first with Object.keys(), Object.values(), or Object.entries():
const person = { name: 'Ada', age: 36 };
for (const [key, value] of Object.entries(person)) {
console.log(`${key}: ${value}`);
}
Best Practices
- Use
for...offor arrays, strings, Maps, Sets, and any other iterable when you need the actual values. - Never use
for...inon arrays or array-likes — the key is a string, order isn’t guaranteed by the array’s design, and inherited properties can leak in. - When you need to loop over a plain object’s properties with
for...of, wrap it withObject.entries(),Object.keys(), orObject.values()instead of reaching forfor...in. - If you must use
for...in, guard the body withObject.hasOwn(obj, key)(or the olderobj.hasOwnProperty(key)) to ignore inherited properties. - Prefer array methods (
map,filter,reduce,forEach) over manual loops when you’re transforming or summarizing data functionally — reach forfor...ofwhen you needbreak/continue,awaitinside the loop, or an earlyreturn. - Avoid mutating a collection’s length while iterating it with either loop; adding or removing elements mid-loop can skip or duplicate entries.
Practice Exercises
- Write a function that takes an array of numbers and uses
for...ofto return their sum. - Given
const scores = { alice: 92, bilal: 85, carla: 78 };, usefor...into log a line like “alice: 92” for each student, then rewrite it usingObject.entries()withfor...ofinstead. - Predict, then explain, what happens when you run
for (const x of { length: 3 }), and describe one way to make that object iterable.
Summary
for...inenumerates an object’s enumerable property keys, including ones inherited through the prototype chain.for...ofiterates the values produced by any object that implements the iterable protocol (Symbol.iterator).- Arrays, strings, Maps, Sets, and generators are iterable; plain objects are not, so
for...ofthrows aTypeErroron them. - Use
Object.entries()/keys()/values()to bridge plain objects intofor...of. - Avoid
for...inon arrays — usefor...of,.entries(), or array methods instead.
