Iteration and array transformation · Lesson 30 of 77 · concept
for...of and for...in: items and keys
About 55 minutes.
By the end of this lesson you can
- Write a for...of loop that reads every value of an array or string.
- Explain why for...of needs no counter and cannot go out of bounds.
- Use for...in to visit the keys of an object, and say what type those keys are.
- Say why for...in is the wrong tool for an array.
- Choose between for, for...of and for...in from what the loop actually needs.
Counting was never the point
Why it matters
Look back at the counting loop from the last lesson. Three-quarters of it is bookkeeping: declare a counter, compare it to a length, add one to it, then use it to look a value up. Only the last part had anything to do with the job. The counter existed to reach the items, not because anybody cared about positions.
That bookkeeping is also where the bugs live. Every off-by-one, every accidentally reversed comparison, every counter that was incremented twice — none of those are possible in a loop that has no counter. So JavaScript has a loop that hands you each item directly and manages the position itself.
The loop that hands you the item
An analogy
Think of the difference between being told 'the folder is third from the left in the second drawer' and having someone put the folder in your hands. Both get you the folder. Only one of them can send you to an empty slot.
for...of is the second one. You name the variable you want each item to arrive in, you name the collection, and the loop does the rest. There is no position to get wrong, and the loop stops exactly when the collection runs out.
Writing a for...of loop
Explanation
The shape is: for (const item of collection) followed by a block. On each pass, item holds the next value. Use const for the item variable, because you are not reassigning it — a fresh binding is created for each pass, so const is not a problem here even though the value differs every time.
for...of works on anything JavaScript calls iterable, which for now means arrays and strings, and later Map and Set from the data-modelling module. Iterating a string gives you its characters one at a time, which is genuinely useful and saves you from indexing into text by hand.
It is not a loop for plain objects. Writing for (const value of someObject) throws a TypeError, because a plain object does not describe how to step through itself. That is not a gap you need to work around — it is what for...in and Object.keys are for, and they are next.
break and continue behave exactly as they did in a for loop. break leaves immediately, continue moves to the next item. This matters: it is the reason a for...of loop is still the right answer when you might need to stop early, even after you learn the array methods later in this module.
for...in walks keys, not values
Explanation
An object is a set of named properties, and sometimes you need to visit all of them without knowing their names in advance — summing every value in a record of scores, say. for (const key in someObject) does that. On each pass, key holds the name of one property, and you read the value with someObject[key].
Two facts about those keys decide how you use them. First, they are always strings, even when they look like numbers. Second, for...in visits inherited enumerable properties as well as the object's own — properties that come from further up the prototype chain, not from the object literal you wrote. For the plain object literals in this module there is nothing inherited to worry about, but when you start working with objects other code created, the defensive form is to test each key with Object.hasOwn(someObject, key) and skip the ones that fail.
The order is defined: integer-like keys come first, in ascending numeric order, and then the remaining string keys in the order they were added to the object. So an object written as b, a, 2, 1 is visited as 1, 2, b, a. Do not lean on that if you can help it — code that depends on key order is fragile for the humans reading it even when the engine agrees — but you should not be surprised by it either.
Check there: for...in iterates over enumerable string-keyed properties including inherited ones, and the documented traversal order puts integer-like keys first in ascending order, then other string keys in insertion order.
The trap: for...in over an array
A common mistake
for...of and for...in look almost identical, and swapping them on an array produces a bug that runs without complaint. for...in over an array gives you its indices — and because for...in keys are always strings, those indices arrive as "0", "1", "2" rather than 0, 1, 2.
The damage shows up the moment you do arithmetic. With a string index, index + 1 is not 1, it is the text "01", because + on a string concatenates. A total built that way turns into a long piece of nonsense text instead of a number, and nothing throws an error to tell you.
There is a second reason to avoid it: for...in visits every enumerable property, and an array is an object, so any extra property somebody attached to it is visited too. Use for...of when you want the values and a counting for loop when you genuinely need the positions as numbers.
Pick the loop that says what you mean
A mental model
Three loops, three questions. Do I need each value? for...of. Do I need each property name of an object? for...in, or Object.keys if you want an array of names you can work with. Do I genuinely need the numeric position — because I am comparing neighbours, or stepping two at a time, or counting backwards? Then a counting for loop, and only then.
Choosing the narrowest loop that does the job is not stylistic fussiness. A loop with no counter cannot have a counter bug, and a reader who sees for...of knows immediately that positions are irrelevant here. The loop you choose is a message to the next person about what the code cares about.
Recap
Recap
for...of gives you values, works on arrays and strings, needs no counter and cannot run off the end. It throws a TypeError on a plain object. for...in gives you property names as strings, includes inherited enumerable properties, and visits integer-like keys first. Using for...in on an array hands you string indices and is almost always a mistake. break and continue work in both.
Worked examples
Every output below was produced by actually running the code in the same sandbox your exercises run in — it is not a prediction.
Reading values with no counter in sight
The same work as a counting loop, with the bookkeeping removed. Compare it mentally with the sum example from the previous lesson: there is no i, no length and no index expression.
1const names = ["Ada", "Grace", "Alan"];2 3for (const name of names) {4 console.log("Hello, " + name);5}- line 3
name is a fresh variable on each pass, holding the value itself — not a position you then have to look up.
- line 4
const is correct here even though the value changes each pass, because each pass gets its own binding.
What it prints
Hello, AdaHello, GraceHello, Alan
Stepping through text one character at a time
A string is iterable, so for...of walks its characters. This is far easier to read than indexing into the string with a counter, and it cannot run off the end.
1const word = "iteration";2let vowels = 0;3 4for (const letter of word) {5 if ("aeiou".includes(letter)) {6 vowels = vowels + 1;7 }8}9 10console.log(vowels);- line 4
letter is a one-character string on each pass.
- line 5
A compact membership test: "aeiou".includes(letter) is true when letter is one of those five characters.
- line 10
i, e, a, i, o — five vowels in the word iteration.
What it prints
5for...in on an object, and the same loop on an array
The first half is for...in used correctly, on an object whose property names you do not know in advance. The second half is the trap: the same loop over an array, printing the type of what it hands you.
1const settings = { theme: "dark", fontSize: 16 };2 3for (const key in settings) {4 console.log(key + " = " + settings[key]);5}6 7const letters = ["a", "b"];8 9for (const index in letters) {10 console.log(index + " is a " + typeof index);11}- line 3
key holds a property NAME. settings[key] is how you get from the name to the value.
- line 10
The index arrives as the text "0", not the number 0 — so index + 1 would be "01", not 1.
What it prints
theme = darkfontSize = 160 is a string1 is a string
Check yourself
Not scored, not stored. Getting one wrong is the useful part.
In for (const index in ["a", "b"]) { ... }, what does index hold on the first pass?
break works the same way in a for...of loop as it did in a counting loop. What does this print?
const word = "loop";for (const letter of word) { if (letter === "o") { break; } console.log(letter);}console.log("done");
What happens when you write for (const value of { a: 1, b: 2 }) { ... }?
Write it yourself
Exercise · core · 6 tests
Find the longest word
Write a function called longestWord that takes an array of words and returns the longest one, using a for...of loop. If two words are tied for longest, return the one that appears first. If the array is empty, return an empty string.
longestWord(words) returns one of the strings from the array — the one with the greatest length. On a tie the earliest one wins, so longestWord(["cat", "dog"]) is "cat". longestWord([]) is "", not undefined.
Define longestWord at the top level of your code — the tests call it by name.
Exercise · core · 6 tests
Total the values of an object
Write a function called sumValues that takes an object whose values are all numbers, and returns the total of those values, using a for...in loop. An object with no properties totals 0. Do not change the object you were given.
sumValues(record) returns a number: every value in the object added together. sumValues({}) is 0. The object still holds exactly the same properties afterwards. Property order does not affect the answer, because addition does not care about order.
Define sumValues at the top level of your code — the tests call it by name.
Worth remembering
- for...of versus for...in — what does each hand you?
for...of hands you the VALUES of an iterable (arrays, strings, Map, Set). for...in hands you the KEYS of an object, always as strings, including inherited enumerable ones. On an array, for...in gives string indices, which is nearly always a bug.
- What happens if you use for...of on a plain object?
A TypeError: a plain object is not iterable. Use for...in for its keys, or Object.keys / Object.values to get an array first.
- In what order does for...in visit keys?
Integer-like keys first, in ascending numeric order, then the remaining string keys in the order they were added. Well defined, but code that depends on it is fragile for the humans reading it.
Check this lesson against the source
We wrote the explanation; we did not invent the facts. This is the page that backs them.
Check there: for...of iterates over the values of iterable objects such as Array and String, supports break and continue, and throws a TypeError for a value that is not iterable.
Check there: for...in should not be used to iterate over an array when index order matters.
Check there: Object.keys returns an array of the object's OWN enumerable string-keyed property names.
Check there: Object.hasOwn returns true only for the object's own properties, not inherited ones.
Prefer to read the source in a structured breakdown? Turn this doc into a breakdown