JAVASCRIPT / LOOPS
for...of for values and for...in for keys
Pick for...of when you want the values in a collection and for...in (or Object.keys) when you want an object's property keys, and explain why they differ.
What you will learn
- Loop the values of arrays, strings, Maps and Sets with for...of
- Turn a plain object into pairs with Object.entries and destructure in the loop head
- Explain why for...in yields string keys, extra properties, and inherited ones
- Predict the TypeError from using for...of on a plain object and fix it
Understanding for...of for values and for...in for keys
The two loops read different things. for...of asks the object for an iterator by looking up its Symbol.iterator method, then repeatedly calls next() and hands you the value each step produces; arrays, strings, Map, Set, arguments and DOM node lists all supply one. for...in never touches Symbol.iterator: it collects the enumerable string-keyed properties of the object and of every object on its prototype chain, and gives you those key names as strings.
An array is an object whose element keys happen to be the strings '0', '1', '2' plus a length property, and that is exactly what for...in exposes: string keys, any extra property you attached to the array, and nothing at all for index positions that were never assigned. The array iterator behind for...of works differently: it counts from 0 up to length - 1 and reads each slot, so it yields elements in order, ignores named properties, and reports undefined for holes. That is why index arithmetic inside a for...in over an array is a trap, because key is '2' rather than 2.
Plain objects have no Symbol.iterator, so for (const v of obj) throws a TypeError before the body runs even once. The usual fix is to convert first with Object.keys, Object.values or Object.entries and loop the resulting array with for...of, which also restricts you to own properties and lets you destructure [key, value] in the loop head. A compact mental model: of walks contents, in walks labels, and for objects you almost always want Object.* plus of rather than in.
Both are real loop statements, so a value you pull out with either form can be used and abandoned mid-way; the choice between them is about what the loop variable holds, not about control flow.
const colors = ['red', 'green', 'blue'];
for (const color of colors) {
console.log('of ->', color);
}
for (const key in colors) {
console.log('in ->', key, typeof key, colors[key]);
}
const settings = { theme: 'dark', fontSize: 14 };
for (const key in settings) {
console.log(key, '=', settings[key]);
}for...of consumes values from an iterator while for...in enumerates property key names, so they answer two different questions about the same data.
Worked examples
A plain object is not iterable
Shows the error for...of raises on an object and the Object.entries conversion that fixes it.
const scores = { ana: 91, luis: 78 };
try {
for (const s of scores) {
console.log(s);
}
} catch (err) {
console.log(err.name, '- a plain object has no Symbol.iterator');
}
for (const [name, score] of Object.entries(scores)) {
console.log(name, 'scored', score);
}Example explained
Line 1for...of looks up scores[Symbol.iterator], finds undefined, and throws before the first iteration.
Line 2err.name is TypeError because the value is the wrong kind of thing, so console.log(s) never runs at all.
Line 3Object.entries(scores) builds [['ana', 91], ['luis', 78]], and an array is iterable.
Line 4The [name, score] pattern in the loop head destructures each pair as it arrives.
for...in on an array gives string keys
Demonstrates that for...in exposes index keys as strings plus any named property, while for...of does not.
const nums = [10, 20, 30];
nums.label = 'sizes';
for (const key in nums) {
console.log(typeof key, key, 'key + 1 =', key + 1);
}
for (const n of nums) {
console.log('value:', n);
}Example explained
Line 1nums.label = 'sizes' adds an own enumerable property, so for...in visits four keys instead of three.
Line 2Every key arrives as a string, so key + 1 concatenates: '0' + 1 is '01', not 1.
Line 3Adding label did not change nums.length, which is still 3.
Line 4for...of uses the array iterator, which walks indices 0 to length - 1, so label is never yielded.
Map data is invisible to for...in
Shows why collections built on internal slots must be read with for...of.
const stock = new Map([['apples', 12], ['pears', 4]]);
for (const [item, count] of stock) {
console.log(item, '->', count);
}
let seen = 0;
for (const key in stock) {
seen++;
}
console.log('for...in visited', seen, 'keys; size is', stock.size);Example explained
Line 1A Map keeps its pairs in internal slots, not as ordinary properties of the object.
Line 2for...of on a Map yields [key, value] arrays in insertion order, so head destructuring works directly.
Line 3for...in reports nothing because Map has no enumerable own properties and its prototype members are non-enumerable.
Line 4Set behaves the same way: for...of yields its members, for...in yields none.
Important notes
On a sparse array such as [1, , 3], for...in skips index 1 because that property does not exist, while for...of reads the slot and yields undefined, so the two loops run a different number of times over the same array.
for...in visits integer-like keys first in ascending numeric order, whatever order you assigned them in, so never use it to reproduce insertion order for numeric-looking keys.
Common mistakes
Using for...in over an array and doing maths on the key: '2' + 1 evaluates to '21', so offsets and sums come out as concatenated strings with no error thrown.
Writing for (const item of myObject) for a plain object: it throws TypeError: myObject is not iterable immediately, and nothing in the body executes.
Assuming for...in lists only own keys: with Object.create(base) or a polluted prototype, inherited enumerable keys appear too, so counts and objects rebuilt inside the loop gain fields you never set. Object.keys(obj) reports own keys only.
Try it yourself
Change, predict, then run
Start from const cart = { pen: 2, book: 1, lamp: 3 }; and print one line per item plus the total quantity using for...of over Object.entries(cart). Then change the loop to iterate cart directly and read the exact error message in the console.
Open the JavaScript workspaceCheck your understanding
Given const arr = ['a', 'b']; arr.extra = 'c'; how many times does a for...of loop over arr run compared with a for...in loop, and why?
- for...of runs twice and for...in runs three times, because for...of stops at length while for...in visits every own enumerable key
- Both run three times, because extra became part of the array once it was assigned
- Both run twice, because an array can only ever iterate its indexed elements
- for...of runs three times and for...in runs twice, because for...of includes named properties
Show answer
Assigning arr.extra adds an own enumerable property but leaves length at 2, and the array iterator used by for...of only reads indices 0 up to length - 1, so it yields 'a' and 'b'. for...in ignores length entirely and enumerates the key names '0', '1' and 'extra', so it runs three times. The 'both run three times' option assumes for...of looks at properties, but it never inspects keys at all; it only takes what the iterator hands back.