for…of & for…in
Loop over collection values with for...of, object keys with for...in, and learn why arrays, strings, mutation, and entries() need different choices.
- 01Choose the right loopUse
for...offor values andfor...infor object keys. - 02Predict tricky outputExplain string keys, emoji iteration, and key order.
- 03Iterate safelyAvoid array
for...inbugs and copy keys before mutating.
Two loops, two questions
JavaScript has two loop forms that look almost the same and answer very different questions. for...of asks “what value comes next?” for...in asks “what property key comes next?” If you remember that one split, most of the lesson becomes predictable.
Use for...of when you want the things: colors, letters, numbers, todo items. Use for...in when you want the labels: property names on an object.
- In real life: Taking apples out of a basket
- In JavaScript:
for...of: each value itself - In real life: Reading labels on a chest of drawers
- In JavaScript:
for...in: each property name - In real life: A label that says “2”
- In JavaScript: Array index keys arrive as strings like
"2"
Where the analogy stops: The basket analogy is about what the loop gives you, not how JavaScript stores arrays internally. Arrays also have property keys, which is why for...in can technically walk them.
This lesson finishes the Control flow module. You already met if, switch, and classic for loops with indexes. Now you will choose the loop that matches the data you are visiting, then move on to Declaring & calling functions.
for...of over arrays: values
STEP THROUGHAn array is an ordered list. The Array basics lesson later teaches arrays fully; for now, read ["red", "green", "blue"] as “three values in order.” A for...of loop takes each value out one at a time.
const colors = ["red", "green", "blue"];for (const color of colors) { console.log(color);}The variable in the loop header can be const because JavaScript creates a fresh color binding for each trip through the loop. You are not reassigning the same const; you are getting a new one each time.
Switch between for...of and for...in. Predict whether the loop variable is a color value or a string key, then step through.
script
for (const color of colors) { console.log(color);}Switch the setting to for...in. The same array now prints keys like 0 is a string. That is not wrong JavaScript; it is the wrong question for the job. You asked for property names, not color values.
for...of over strings: characters
INTERACTIVEStrings are iterable too. That means for...of can ask a string for its next character. This is especially useful with Unicode. The Strings lesson showed that "😀".length is 2; that is a storage detail, not how many visible characters a person sees.
Step through an emoji with for...of, then with an index loop. The same string produces different visits.
script
for (const character of face) { console.log(character);}for (let i = 0; i < face.length; i++) { console.log(JSON.stringify(face[i]));}for...of follows the string iterator, which visits code points. A classic index loop visits storage positions. Some symbols, including many emoji, use two positions, so an index loop can expose two halves. A later Unicode lesson goes deeper.
for...in over object keys
STEP THROUGHObjects are collections of properties: a key such as name points to a value such as "Ada". The Object basics lesson covers this in depth. A for...in loop walks enumerable property keys.
Predict the key order before stepping. The object was written as b, 2, a, 1, but integer-like keys come first.
script
for (const key in order) { console.log(key + ":" + order[key]);}Key order is defined for ordinary objects: integer-like keys first in ascending order, then other string keys in insertion order. Every key you receive is still a string. If an object inherits enumerable properties, for...in can see those too, so guard when you only want the object’s own keys.
const drawer = Object.create({ inherited: "from prototype" });drawer.name = "Ada";for (const key in drawer) { if (Object.hasOwn(drawer, key)) { console.log("own:" + key); } else { console.log("inherited:" + key); }}own:nameinherited:inherited
The object has one own key, name, and one inherited enumerable key, inherited. Object.hasOwn tells them apart.
Object.create only to make inherited enumerable properties visible without changing any real prototype.Object.hasOwn(object, key) is true only for keys directly on that object. It filters out inherited enumerable keys. You will revisit ownership and enumerability in the Enumerability & ownership lesson.
Why not for...in on arrays?
Arrays are objects too, so for...in can walk their keys. That is exactly why it is a poor default: array keys are strings, and extra enumerable properties can appear.
If your job is to greet each person, reading “seat 0, seat 1, seat 2” is a detour. It can even pick up labels that are not seats.
- In real life: Greeting people in the seats
- In JavaScript:
for...ofover array values - In real life: Reading the seat numbers
- In JavaScript:
for...inover array keys - In real life: A sticky note on the aisle
- In JavaScript: An extra property such as
arr.extra
Where the analogy stops: Sometimes you really do need seat numbers. In JavaScript that is usually a classic index for loop, because the index is a number you control.
const colors = ["red", "green"];colors.extra = "paint";for (const key in colors) console.log(key);for (const color of colors) console.log(color);The for...in loop prints 0, 1, and extra. The for...of loop prints red and green. If you need numeric indexes, use the classic for (let i = 0; i < array.length; i++) pattern from Lesson 6.3. If you need values, use for...of.
| Loop | What you get | Works on | Can break? | Best for |
|---|---|---|---|---|
for | An index number | Arrays and strings with positions | Yes | When you need index math or exact start/stop control |
for...of | A value | Iterables: arrays, strings, maps, sets, and more later | Yes | Reading each item in order |
for...in | A property key string | Objects; also sees enumerable inherited keys | Yes | Inspecting object keys carefully |
forEach | A value passed to a function | Arrays | No normal break | Short array actions after the Array basics lesson |
Changing something while iterating it
INTERACTIVEA loop does not freeze the thing it is visiting. If you delete an object property before for...in reaches it, that property will not be visited. If you add properties during the loop, whether they are visited is not something to rely on. With arrays, for...of can continue to newly pushed items, as the bounded example shows.
const cart = { keep: 1, remove: 2, later: 3 };for (const key in cart) { if (key === "keep") { delete cart.remove; cart.added = 4; } console.log(key);}const numbers = [1, 2];for (const number of numbers) { console.log(number); if (number === 1) numbers.push(3);}Object loop:
keep,later
Array loop:
1,2,3
Deleting remove before it is visited prevents it from printing. Pushing 3 during an array for...of makes this bounded loop continue to the new item.
for...in are not something to rely on; iterate a copy when the object may change.If you plan to add or delete while looping, first make a list of what to visit: for (const key of Object.keys(object)) or for (const item of [...array]). Object.keys, Object.values, and Object.entries get a full lesson later.
entries() with destructuring
INTERACTIVESometimes you need both pieces: the index and the value, or the key and the value. entries() gives pairs. Destructuring is the bracket pattern that unpacks a pair into names.
const colors = ["red", "green"];for (const [index, color] of colors.entries()) { console.log(index + ":" + color);}const user = { name: "Ada", role: "admin" };for (const [key, value] of Object.entries(user)) { console.log(key + "=" + value);}0:red1:greenname=Adarole=admin
entries() creates pairs. The [index, color] and [key, value] patterns unpack each pair into two names.
Read for (const [index, color] of colors.entries()) as “for each pair, put the first part in index and the second part in color.” The Destructuring lesson will make this pattern feel normal.
Where you’ll use this
These loops appear in everyday code whenever you read lists, strings, or small configuration objects.
- Render each item in a menu:
for...ofover an array of labels. - Count characters in a word game:
for...ofover a string. - Validate settings:
for...inover an object’s keys, guarded withObject.hasOwn. - Show key/value rows:
Object.entrieswith destructuring.
- Print each color in
["red", "green"] - Count letters in a short string
- Print the keys in
{ name: "Ada", role: "admin" } - Inspect a dictionary that might inherit extra keys
- Add one to every array index
- Loop an array that someone gave an
extraproperty
Sort each task by the loop choice that best matches it. A third bucket catches array cases where for...in is the trap.
Common misconceptions
“for...in means in the array.”
No. It means “in the object’s enumerable keys.” Arrays have keys, but array work usually wants values.
“Array indexes from for...in are numbers.”
They are strings. That is why "0" + 1 becomes "01".
“for...of works on every object.”
Plain object literals are not iterable by default. Use Object.keys, Object.values, or Object.entries when you need iterable lists from them.
“Changing a collection during a loop is always safe.”
It is easy to skip, repeat, or unexpectedly include values. Copy first when you can.
“Key order is random.”
Ordinary object key order is defined, but you should still choose data structures for your intent instead of leaning on key order as a design.
Practice: choose and trace loops
5 EXERCISESPredict what the program prints. Do not write the values a, b, and c; this loop asks for keys.
const letters = ["a", "b", "c"];
for (const key in letters) {
console.log(key);
}The loop prints the array’s keys: 0, 1, and 2. Type them as 0,1,2.
Run or trace this vowel counter. What number prints?
const word = "education";
let count = 0;
for (const character of word) {
if ("aeiou".includes(character)) {
count = count + 1;
}
}
console.log(count);const word = "education";
let count = 0;
for (const character of word) {
if ("aeiou".includes(character)) {
count = count + 1;
}
}
console.log(count);The loop visits each character. The if counts only characters included in aeiou, so the final count is 5.
What is the first line printed by this Object.entries loop?
const user = { name: "Ada", role: "admin" };
for (const [key, value] of Object.entries(user)) {
console.log(key + ": " + value);
}const user = { name: "Ada", role: "admin" };
for (const [key, value] of Object.entries(user)) {
console.log(key + ": " + value);
}Destructuring puts name in key and Ada in value, so the first printed line is name: Ada.
The bug prints 01, 11, and 21. Fix it so the displayed positions are 1, 2, and 3.
const items = [10, 20, 30];
for (const key in items) {
console.log(key + 1); // prints "01", "11", "21"
}const items = [10, 20, 30];
for (let index = 0; index < items.length; index++) {
console.log(index + 1);
}The classic index loop creates numeric index values. Now index + 1 prints 1, 2, and 3 instead of string concatenation.
Use the copy-first pattern to remove false flags. Which keys remain?
const flags = { keep: true, remove: false, later: true };
for (const key of Object.keys(flags)) {
if (flags[key] === false) {
delete flags[key];
}
}
console.log(Object.keys(flags).join(","));const flags = { keep: true, remove: false, later: true };
for (const key of Object.keys(flags)) {
if (flags[key] === false) {
delete flags[key];
}
}
console.log(Object.keys(flags).join(","));Looping over Object.keys(flags) gives a separate list to visit. Deleting remove from the object does not change the list being looped, so the remaining keys are keep,later.
Quiz: check your understanding
7 QUESTIONSPredict before you choose. Every option explains why it is right or wrong.
Question 1 of 7What does
for...ofgive you when looping over an array?Choose an answer to see the explanation.
Question 2 of 7What does this print?
Read the code, then predictconst colors = ["red", "green"]; for (const key in colors) { console.log(typeof key); }Choose an answer to see the explanation.
Question 3 of 7Which loop is best for printing every character in
"héllo"?Choose an answer to see the explanation.
Question 4 of 7What keys does this object loop print?
Read the code, then predictconst object = { b: 1, 2: "x", a: 2, 1: "y" }; for (const key in object) { console.log(key); }Choose an answer to see the explanation.
Question 5 of 7Why is
for...ina poor default for arrays?Choose an answer to see the explanation.
Question 6 of 7What does this print in current JavaScript engines?
Read the code, then predictconst numbers = [1, 2]; for (const number of numbers) { console.log(number); if (number === 1) numbers.push(3); }Choose an answer to see the explanation.
Question 7 of 7What does
.entries()plus destructuring give you?Choose an answer to see the explanation.
Key takeaways
for...ofloops over values from an iterable, such as an array or string.for...inloops over enumerable property keys, which are strings and may include inherited keys.- Avoid
for...inas your default array loop; usefor...offor values or classicforfor indexes. - Deleting an object property before
for...inreaches it means it is not visited; added properties are not reliable. entries()plus destructuring is the friendly way to get index/value or key/value pairs.
Remember the one-liner.for...of gives values; for...in gives keys.
Up next: Declaring & calling functions.