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...ofwhen you want values andfor...inwhen you want the keys of an object. - 02Predict tricky outputExplain why array keys are strings, why an emoji can split in two, and what order object keys come in.
- 03Loop safelyAvoid
for...inbugs on arrays, and loop over a copy when you change data during a loop.
Two loops, two questions
JavaScript has two loops that look almost the same: for...of and for...in. They answer different questions.
for...ofasks: "What is the next value?"for...inasks: "What is the next key?"
A key is the name of a property, such as name in { name: "Ada" }. Remember this one split, and most of the lesson is easy to predict.
You have a basket of apples and a chest of drawers. To eat the apples, you take each apple out. To find a drawer, you read the labels on the front. One job wants the things. The other job wants the names.
- In real life: Take each apple out of a basket
- In JavaScript:
for...of: you get each value - In real life: Read each label on a chest of drawers
- In JavaScript:
for...in: you get each key - In real life: A drawer label that says “2”
- In JavaScript: An array key is a string, like
"2"
for...of hands you the cards: red, green, blue. for...in hands you the keys under them: "0", "1", "2", as strings.This lesson is the last one in the Control flow module. You already know if, switch, and the counting for loop. Here you learn to pick the loop that fits your data. After this lesson, you move on to Declaring & calling functions.
An iterable is a value that can hand out its items one at a time. Arrays and strings are iterables, so for...of works on them. A plain object is not an iterable. It holds named properties, so for...in can ask it for those names.
for...of on arrays: values
STEP THROUGHAn array is a list of values in order. The Array basics lesson covers arrays fully. For now, read ["red", "green", "blue"] as "three values in order". A for...of loop takes out one value at a time.
const colors = ["red", "green", "blue"];for (const color of colors) { console.log(color);}Line 1 makes a list of three colors. Line 2 says: take one value from colors and call it color. Line 3 prints that value. The console shows red, then green, then blue, each on its own line.
The loop variable color can be a const. That is because JavaScript makes a new color variable for each pass through the loop. You never change one const. Each pass gets its own.
const prices = [3, 5, 2];let total = 0;for (const price of prices) { total = total + price;}console.log(total);Here price is 3, then 5, then 2. Line 4 adds each price to total. So the program prints 10. This is a common job for for...of: read each value, without caring where it sits in the array.
Switch between for...of and for...in. Predict whether the loop variable holds a color value or a string key, then step through.
script
for (const color of colors) { console.log(color);}Now switch the setting to for...in. The same array now prints keys, such as 0 is a string. This is still valid JavaScript. But it answers a different question. You asked for keys, not for color values.
for...of on strings: characters
INTERACTIVEStrings are iterables too. So for...of can ask a string for its next character. This helps with emoji. The Strings lesson showed that "😀".length is 2. That number counts how JavaScript stores the emoji. It is not how many characters a person sees.
for (const character of "tea") { console.log(character);}The loop asks the string "tea" for one character at a time. It prints t, then e, then a, each on its own line. This works the same for a longer name or a sentence. Spaces count as characters too.
The step-through below uses an emoji. Line 1 stores one emoji in face. Lines 2 and 3 run once and print 😀. Then a counting loop on lines 5 and 6 reads position 0 and position 1. It runs twice. JSON.stringify turns each stored half into text you can read, such as "\ud83d".
For short, plain text, both loops look the same. When you mean characters, for...of is the safer choice.
Step through one emoji with for...of, then with a counting loop. The same string gives different results.
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 keeps the emoji whole and gives it to you once. An index loop reads the two slots the emoji is stored in, so it gives you two halves.JavaScript stores a string in slots. Most letters take one slot. Many emoji take two slots. A counting loop reads slots, so it can split an emoji into two halves. for...of reads whole characters, so it keeps the two halves together. A later Unicode lesson goes deeper.
for...in on objects: keys
STEP THROUGHAn object holds properties. Each property has a key and a value. For example, the key name can hold the value "Ada". The Object basics lesson covers objects fully. A for...in loop visits the keys of an object, one at a time.
const profile = { name: "Asha", city: "Delhi" };for (const key in profile) { console.log(key + ": " + profile[key]);}On the first pass, key is name. On the second pass, it is city. Line 3 uses square brackets, profile[key], to read the value for the current key. So the program prints name: Asha and then city: Delhi.
The step-through below uses an object with four keys, written in the order b, 2, a, 1. Line 2 puts one key at a time in key. Line 3 uses square brackets to read that key's value. The output is 1:y, 2:x, b:1, and then a:2. You need square brackets here because the key changes on every pass.
Predict the key order before you step. The keys were written as b, 2, a, 1, but keys that look like whole numbers come first.
script
for (const key in order) { console.log(key + ":" + order[key]);}Why that order? For a normal object, JavaScript visits keys in a fixed order:
- First, keys that look like whole numbers, such as
1and2, from smallest to largest. - Then all other keys, in the order they were added.
Every key you get is a string, even "1" and "2".
An object can also be made from a parent object. It can then use the parent's keys as if they were its own. These keys are called inherited keys. The keys that sit directly on the object are its own keys. for...in visits both kinds. So check each key when you only want the object's own keys.
Built-in keys, such as toString, are hidden from loops. You won't see them in a for...in loop. Keys you add yourself are not hidden.
The demo below has one own key and one inherited key. Read the output first. Then turn on the checkbox. It makes the loop skip keys that do not sit directly on drawer. Reset turns the checkbox off again, so you can compare the two outputs.
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 key, inherited, from its parent. Turn on the checkbox to skip the inherited key.
Object.create to give drawer a parent object. That is how it gets an inherited key.Object.hasOwn(object, key) gives true only when the key sits directly on that object. For an inherited key, it gives false. The Enumerability & ownership lesson covers this in more detail.
const settings = Object.create({ debug: true });settings.theme = "light";for (const key in settings) { if (Object.hasOwn(settings, key)) console.log(key);}theme sits directly on settings, so it is an own key. debug sits on the parent object. for...in visits both. Object.hasOwn(settings, key) is true only for theme.Line 1 uses Object.create to make settings with a parent object. The parent holds debug. Line 2 adds theme directly to settings. The loop on line 3 visits both keys. The check on line 4 skips debug, so the program prints only theme. Use this check whenever an object might have a parent you did not make yourself.
Why not use for...in on arrays?
An array is a kind of object, so for...in can loop over its keys. But that is a poor choice for arrays, for two reasons:
- The keys are strings, such as
"0", not numbers. - Extra properties that someone added to the array show up too.
You are in a theater, and your job is to greet each person. Reading out "seat 0, seat 1, seat 2" does not greet anyone. You might even read out a sticky note on the aisle, which is not a seat at all.
- In real life: Greet each person in their seat
- In JavaScript:
for...ofgives the array values - In real life: Read each seat number
- In JavaScript:
for...ingives the array keys - In real life: A sticky note on the aisle
- In JavaScript: An extra property, such as
colors.extra
const colors = ["red", "green"];colors.extra = "paint";for (const key in colors) console.log(key);for (const color of colors) console.log(color);Line 2 adds an extra property called extra to the array. The for...in loop on line 3 prints 0, 1, and extra. The for...of loop on line 4 prints red and green.
Neither loop prints paint. Line 3 prints the key extra, not its value. Line 4 reads only the array's items, so it skips the extra property. So for...in does run on an array. It is just the wrong tool for a list of items.
- Need the values? Use
for...of. - Need the position numbers? Use the counting loop from the Loops lesson:
for (let i = 0; i < array.length; i++).
The table compares four ways to loop. Read each row as: "this loop gives me this, works on this, and fits this job."
| Loop | What the loop variable holds | What it works on | Can you stop early with break? | Use it when |
|---|---|---|---|---|
for | A number: the position, such as 0. | Arrays and strings, which have numbered positions. | Yes. | You need the position number, or exact control over where to start and stop. |
for...of | A value, such as "red". | Arrays, strings, and other values that hand out items one by one. | Yes. | You want to read each item in order. |
for...in | A key, as a string, such as "name". | Objects. It also sees keys the object gets from its parent. | Yes. | You want the key names of an object. |
forEach | A value, given to a function you write. | Arrays. | No. break does not work inside it. | You want to run one function for each array item. You'll meet it after the Array basics lesson. |
Changing an object or array during a loop
INTERACTIVEA loop does not freeze the object or array it is looping over. If your code changes it during the loop, the loop can see the change. Here are the rules:
- Object,
for...in: if you delete a property before the loop reaches it, the loop skips it. - Object,
for...in: if you add a property, the loop may or may not visit it. Do not rely on either. - Array,
for...of: if you push a new item onto the end, the loop goes on to visit it.
Read the object example in order. The loop visits keep first. Then line 4 deletes remove, and line 7 prints keep. Next the loop visits later. It never prints remove, because that property no longer exists. Line 5 also adds a property, added. This lesson makes no promise about whether the loop visits it.
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
Line 4 deletes remove before the loop reaches it, so remove never prints. In the array loop, pushing 3 makes the loop go on and visit it.
for...in may or may not be visited. Loop over a copy when the object might change.Do you plan to add or delete during the loop? Then first make a separate list of what to visit, and loop over that list. For an object, use for (const key of Object.keys(object)). For an array, use for (const item of [...array]). Object.keys, Object.values, and Object.entries get a full lesson later.
const cart = ["tea", "milk"];for (const item of [...cart]) { console.log(item); if (item === "tea") cart.push("bread");}console.log(cart.join(","));tea and milk. Line 4 adds bread to the real cart. The loop never sees it.On line 2, [...cart] makes a new array with tea and milk. The loop visits this copy. Line 4 adds bread to the real cart, but not to the copy. So the loop prints only tea and milk. The last line prints tea,milk,bread. That proves the real cart did change.
Get two things at once with entries()
INTERACTIVESometimes you need two things on each pass: the position and the value, or the key and the value.
colors.entries()gives pairs like[0, "red"]: a position and a value.Object.entries(user)gives pairs like["name", "Ada"]: a key and a value.
Destructuring unpacks a pair into two named variables. You write the two names inside square brackets: [index, color].
In the array example, the first pair is [0, "red"]. Destructuring puts 0 in index and "red" in color. In the object example, the first pair is ["name", "Ada"]. So the output starts with 0:red, and later it shows name=Ada.
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() makes pairs. [index, color] and [key, value] unpack each pair into two named variables.
Read for (const [index, color] of colors.entries()) like this: "for each pair, put the first part in index and the second part in color." The Destructuring lesson covers this in full.
Where you'll use this
You will use these loops whenever you read a list, a string, or a small object of settings.
First ask what you need, then pick the loop:
- "What is the next menu label?" asks for a value. Use
for...of. - "Which setting name should I check?" asks for a key. Use
for...inwith theObject.hasOwncheck. - Need both the key and the value? Make pairs with
Object.entries, and loop over the pairs withfor...of.
const menu = [ { label: "Tea", available: true }, { label: "Cake", available: false },];for (const item of menu) { if (item.available) console.log(item.label);}On each pass, item is one menu object. The if on line 6 lets only available items through. So this prints Tea and skips Cake. A web page often does this: it gets a list from a server, then decides which items to show.
- Show each item in a menu:
for...ofover an array of labels. - Count letters in a word game:
for...ofover a string. - Check settings:
for...inover an object's keys, with theObject.hasOwncheck. - Show rows of keys and values:
Object.entrieswith destructuring.
- Print each color in
["red", "green"] - Count the letters in a short string
- Print the keys in
{ name: "Ada", role: "admin" } - Check the keys of an object that might get extra keys from a parent
- Add one to every array position
- Loop over an array that someone gave an
extraproperty
Sort each task into the loop that fits it. The third group is for array tasks where for...in causes a bug.
Common misconceptions
"for...in means 'each item in the array'."
No. It means "each key in the object". Arrays have keys, but array code usually wants the values.
"The array keys from for...in are numbers."
No, they are strings. That is why "0" + 1 gives "01": + joins the text instead of adding.
"for...of works on every object."
No. A plain object like { name: "Ada" } is not an iterable, so for...of throws an error. Use Object.keys, Object.values, or Object.entries to get an array from it first.
"Changing an object or array during a loop is always safe."
No. The loop can skip items, or visit items you did not expect. When you can, loop over a copy.
"Object keys come out in a random order."
No. The order is fixed: keys that look like whole numbers come first, then other keys in the order they were added. Still, don't build your program around key order. If order matters, use an array.
Practice: choose and trace loops
5 EXERCISESPredict what the program prints. The answer is not 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 does it print?
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 adds 1 only when the character is one of aeiou. education has five vowels, so it prints 5.
What is the first line this Object.entries loop prints?
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 line printed is name: Ada.
This program prints 01, 11, and 21. Fix it so it prints the positions 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 counting loop makes index a number. Now index + 1 adds, and prints 1, 2, and 3. Before, + joined text.
This program loops over a copy of the keys and deletes every flag that is false. Which keys are left?
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(","));Object.keys(flags) makes a separate list of keys. Deleting remove from the object does not change that list. The keys left are keep,later.
Quiz: check your understanding
7 QUESTIONSPredict the answer before you choose. Every choice explains why it is right or wrong.
For each code question, find the word between the loop variable and the data. of means the variable gets a value. in means it gets a key. Then trace one pass of the loop before you look at the choices.
Question 1 of 7What does
for...ofgive you when it loops 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 choice for looping over an array?Choose an answer to see the explanation.
Question 6 of 7This loop pushes a new number while it runs. What does it print?
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()with destructuring give you?Choose an answer to see the explanation.
Key takeaways
for...ofgives you each value from an iterable, such as an array or a string.for...ingives you each key of an object. Keys are strings, and they can include keys from a parent object.- Don't use
for...into loop over an array. Usefor...offor values, or a countingforloop for positions. - If you delete a property before
for...inreaches it, the loop skips it. Added properties may or may not be visited. entries()with destructuring gives you two things at once: position and value, or key and value.
Remember the one-liner.for...of gives values; for...in gives keys.
Up next: Declaring & calling functions.