Topic 2.7
for...of and for...in
In one line
for...of walks through the values of anything iterable (arrays, strings, Maps, Sets), while for...in walks through the property names (keys) of an object, including inherited ones. Use for...of for lists and Object.keys/entries with for...of for objects.
Think of it like this
A bookshelf and a filing cabinet. To read every book on the shelf, you take them one book at a time, in order: that's for...of, which gives you each value. A filing cabinet has labelled drawers; for...in reads you the labels on the drawers (the keys), not what's inside, and it even reads out labels on drawers inherited from the cabinet underneath.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Iterable
- A value whose items can be taken one at a time in order, such as an array, string,
MaporSet. - for...of
- A loop that gives you each value of an iterable.
- for...in
- A loop that gives you each property name (key) of an object, as a string, including inherited ones.
- Key / property name
- The label of a value in an object, like
namein{ name: "Asha" }. - Enumerable
- A property that shows up in
for...inandObject.keys. Properties you add normally are enumerable; built-in methods are not. - Own vs inherited
- An own property is stored on the object itself; an inherited one comes from its prototype (a parent object it borrows from).
- Object.entries
- A function that turns an object into an array of
[key, value]pairs, ready forfor...of.
Step by step
01Loop over values with for...of
No index and no length check: the loop hands you each value in order and stops after the last one.
const colours = ["red", "green", "blue"];
for (const colour of colours) {
console.log(colour);
}02It works on every iterable
Strings give characters, Sets give values, Maps give [key, value] pairs. Same loop, different collections.
for (const ch of "hey") console.log(ch); // h, e, y
for (const n of new Set([1, 1, 2])) console.log(n); // 1, 2
for (const [k, v] of new Map([["a", 1]])) console.log(k, v); // a 103Need the index? Use entries()
array.entries() gives [index, value] pairs, and destructuring unpacks them into two variables.
const podium = ["Asha", "Ravi", "Meera"];
for (const [i, name] of podium.entries()) {
console.log(i + 1, name);
}04Loop over an object's keys with for...in
for...in gives keys as strings. Read the value with brackets: obj[key] (dot notation can't use a variable).
const user = { name: "Asha", age: 30 };
for (const key in user) {
console.log(key, "=", user[key]);
}05Pick the right loop
Use this decision path whenever you need to loop over something.
Try it yourself
- 1
Loop over a word
In the first example, add
for (const ch of "loop") console.log(ch);. Predict the four lines, then run. - 2
Swap in and of
In the second example, change the first loop to
for (const key of user). Predict what happens (see Break it) before you run. - 3
Add a key
In the third example, add
book.pages = 180;before the loop. Predict wherepagesappears on the page and in the logged keys.
Code & diagrams
The emoji counts as 2 in `.length` but as one character in `for...of`. The Set drops the duplicate `js`.
Preview
Console
Nothing logged yet.Expected console output
0 apple
1 banana
2 cherry
length: 4 characters: 3
tag js
tag css
pens 12
books 3`for...in` gave string keys (`"0" + 1` is `"01"`) and the extra `label` property. `for...of` gave exactly the three values.
Console
(nothing was printed)Expected console output
name -> Asha
age -> 30
city -> Pune
1 2 b a
0 string 01
1 string 11
2 string 21
label string label1
10
20
30`Object.create(defaults)` makes an object that inherits from `defaults` (prototypes are Topic 7.3). `for...in` shows the inherited `theme`; `Object.keys` and `Object.hasOwn` don't.
Preview
Console
Nothing logged yet.Expected console output
[ 'title', 'author', 'year' ]
for...in: fontSize
for...in: theme
own only: fontSize
[ 'fontSize' ]Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use for...of on a plain object
In the second example, change for (const key in user) to for (const key of user).
Break #2
Do maths with for...in indexes
Loop over an array with for (const i in nums) and compute the next index with i + 1.
Myth vs fact
Myth
for...in is the way to loop over an array.
Fact
It loops over property names as strings, includes non-index properties, and may include inherited ones. Use for...of or a counting loop for arrays.
Myth
for...of works on any object.
Fact
It only works on iterables. Plain objects aren't iterable; use Object.entries(obj) to get an iterable array of pairs.
Myth
Object keys come out in a random order.
Fact
The order is defined by the spec: integer-like keys ascending, then string keys in insertion order (then symbols, which loops skip). Don't rely on it for meaning, but it is predictable.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
for...ofuses the iteration protocol: it callsobj[Symbol.iterator]()to get an iterator, then callsnext()until it returns{ done: true }. Any object can become iterable by defining that method; you'll do it in Topic 14.1. On early exit (break,return, throw) it calls the iterator'sreturn()method. - ▸
For plain arrays, V8 optimises
for...ofto roughly the speed of an indexedforloop. Differences only show up in micro-benchmarks; choose by clarity. - ▸
Mutating a collection while iterating has defined but surprising behaviour: an array iterator reads
lengthon every step, so pushing insidefor...ofcan loop forever; aSetorMapvisits entries added during iteration.for...indoesn't guarantee whether properties added during the loop are visited. Iterate over a copy if you need to change the original. - ▸
Live DOM collections like
element.childrenare iterable, and removing children while looping over them skips items, because the collection shrinks under you. Loop over a static copy ([...element.children]orArray.from(...)) instead (Phase 8).
Remember this
- 1
**
for...of(since ES2015) loops over the values of an iterable**. An iterable is any value that knows how to hand out its items one by one, in order: arrays, strings,Map,Set, theargumentsobject, DOM lists likeNodeList, and generators. The syntax isfor (const item of list) { ... }. No counter, no index, no off-by-one: you get each item directly. Useconstunless you reassign the variable inside the body; each iteration gets a fresh binding. - 2
On a string,
for...ofgives you one character at a time, and it handles emoji and other characters outside the basic range correctly (they are one character tofor...of, even though.lengthcounts them as 2; Topic 4.2 explains why). On a Map it gives[key, value]pairs; on a Set it gives each value. If you need the index of an array item as well, loop overarray.entries(), which yields[index, value]pairs:for (const [i, item] of list.entries())(the[i, item]part is destructuring, Topic 5.8). - 3
Plain objects are not iterable.
for (const x of { a: 1 })throwsTypeError: ... is not iterable. To loop over an object, turn it into an array first:Object.keys(obj)(the keys),Object.values(obj)(the values) orObject.entries(obj)([key, value]pairs), then usefor...of(covered in depth in Topic 6.4). These only include the object's own properties, which is usually what you want. - 4
**
for...inloops over the keys (property names) of an object, as strings**:for (const key in obj) { console.log(key, obj[key]); }. It visits every enumerable property (properties that are meant to show up in loops; built-in ones liketoStringare not enumerable), and it also walks up the prototype chain, visiting inherited enumerable properties (prototypes are Topic 7.3). The order is: integer-like keys in ascending order first, then the other string keys in the order they were added. - 5
**Don't use
for...inon arrays. It gives the indexes as strings** ("0","1"), sokey + 1becomes"01"; it also visits any extra properties added to the array, and inherited ones if someone added toArray.prototype. For arrays usefor...of, a countingforloop (Topic 2.5), or array methods (Phase 5). - 6
Which to use: values of a list, string,
MaporSet→for...of. Keys and values of a plain object →for (const [key, value] of Object.entries(obj)). Need the index →forwith a counter, orentries().for...inis mostly for old code and quick inspection of an object; if you use it, guard withObject.hasOwn(obj, key)(ES2022) to skip inherited keys. A handy memory trick: **"of" gives values, "in" gives keys (names that are *in* the object).**
Explain it without notes
What is the difference between for...of and for...in?
Why shouldn't you use for...in to loop over an array?
How do you loop over the keys and values of a plain object?
What does "iterable" mean? Name four built-in iterables.
Practice
Given const prices = { tea: 20, coffee: 35, juice: 50 };, print each item and price as tea: 20 and then the total.
Count the vowels in the string "Hectal teaches JavaScript" with for...of.
Print each item of ["a", "b", "c"] with its position starting from 1, like 1. a.
Trade-offs
- ↔
for...ofis the clearest loop for values and supportsbreak/continue; array methods likemapandfilter(Phase 5) are clearer still when you're building a new array. - ↔
for...inis short for inspecting an object's keys but drags in inherited properties;Object.entrieswithfor...ofis a little longer and does exactly what you mean.
Done when you can
I can loop over arrays, strings, Maps and Sets with
for...of.I can get the index too, with
entries().I can loop over a plain object with
Object.entries/keys/values.I know
for...ingives string keys and includes inherited ones.I can explain why
for...inis wrong for arrays.