Learn by reading through in order

Loops — for, for...of, and for...in

Learn JavaScript loops from the ground up: for for counting, for...of for stepping through elements, for...in for keys, and how to build nested loops.

To print the price of each ordered item on its own line, you'd have to write the same line 3 times for 3 items and 10 times for 10. The number of items depends on the data, so you can't know in advance how many lines you'll need.

This article covers for, which repeats by counting, for...of, which hands you elements one at a time, and for...in, which walks through keys.

Repeating Code Once per Item — The Three Expressions of for

Say you want to print the item names from an order, one per line. If you write them out one by one, starting with console.log(orderLines[0]), you'll have to add a line as soon as the order grows to 4 items, and edit the code every time the count changes.

A for loop (a statement that repeats the same code while counting) takes three expressions in its parentheses, separated by semicolons, and the code you want to repeat goes inside the curly braces. What's inside those braces is called the body. From left to right, the three expressions are the initialization, the condition, and the update: the condition is checked before entering the body, and the update runs after the body.

i++ means the same thing as i = i + 1. Since its value changes on every pass, i is declared with let.

const orderLines = ["Mug", "Notebook", "Ballpoint pen"];   // item names in the order

// Start i at 0 and add 1 with i++ until it reaches the item count
for (let i = 0; i < orderLines.length; i++) {
  // Indexes start at 0, so display i + 1 to count from item 1
  console.log(`Item ${i + 1}: ${orderLines[i]}`);
}
// Item 1: Mug
// Item 2: Notebook
// Item 3: Ballpoint pen

console.log(`Items shown: ${orderLines.length}`);   // Items shown: 3
One Pass Through a for Loop
Initializationlet i = 0Conditioni < 3Bodyprints 1 itemUpdatei++Loop endstruecheck againfalse

The initialization runs only once at the very start, the condition runs before the body, and the update runs after the body. On the pass where the condition becomes false, the loop ends without running the body.

Checking the condition, running the body, and running the update together make up one pass (an iteration). Because the update runs after the body, i always equals the number of times the body has already finished, so on the first pass the body runs with i still at 0.

The table below shows i and the condition check on each pass.

PassValue of i and the conditionWhat happens in the body
Pass 1i = 0 / 0 < 3 is trueItem 1: Mug
Pass 2i = 1 / 1 < 3 is trueItem 2: Notebook
Pass 3i = 2 / 2 < 3 is trueItem 3: Ballpoint pen
Start of pass 4i = 3 / 3 < 3 is falseThe body doesn't run; the loop ends

Reading Past the End of an Array Gives undefined

If you write the condition as i <= orderLines.length, you get one extra pass at the end, which reads orderLines[3] — an element that doesn't exist. Anything out of range is undefined, so you'll see "Item 4: undefined," and if the loop is adding up prices, the result becomes NaN. Keep the loop running only while the index is less than the item count.

Add up the prices of the items in a shopping cart. The price array unitPrices is already declared.

① Set up a variable for the total, starting at 0.

② Use for to count up from 0, adding each price to the total.

③ Print the total in the form "Total: ◯◯ yen".

④ Divide by the number of items to get the average, round down, and print it in the form "Average: ◯◯ yen".

(If it runs correctly, an explanation will appear.)

JavaScript / TypeScript Editor

Run code to see output

Getting Each Element Directly — for...of and entries

Say you want to process a list of order numbers waiting to ship, one after another. With for, you'd declare an index variable, write the condition and the update, and then read each one with orderIds[i] — and a typo in the index means reading the wrong element.

for...of (a statement that takes the elements of an array or other iterable one at a time and puts each into a variable) is written as for (const orderId of orderIds), with only the declaration and the array inside the parentheses.

When you need the position too, loop over orderIds.entries(). On each pass it gives you a pair like [0, "A-1001"], which you unpack with square brackets as [index, orderId].

const orderIds = ["A-1001", "A-1002", "A-1003"];   // order numbers waiting to ship

// orderId holds the element itself (no index in sight)
for (const orderId of orderIds) {
  console.log(`Awaiting shipment: ${orderId}`);
}
// Awaiting shipment: A-1001 … (3 lines)

// When you also need the index, get pairs from entries()
for (const [index, orderId] of orderIds.entries()) {
  console.log(`#${index + 1}: ${orderId}`);
}
// #1: A-1001
// #2: A-1002
// #3: A-1003
How you receive itWhat goes into the variablePrinted line
Get elements with ofThe element itself (A-1001)Awaiting shipment: A-1001
Unpack entries() into [index, orderId]index is 0 / orderId is A-1001#1: A-1001
Receive entries() in a single variableThe whole index-and-element pairStill the array [0, "A-1001"]

const [index, orderId] works the same way as unpacking arrays in the destructuring article: the index goes into the first position and the element into the second. A new variable is created on every pass, so you can declare a for...of loop variable with const without getting a reassignment error.

Find the orders that haven't arrived yet in a list of delivery statuses. The array deliveries is already declared; each order has an order number (id) and a status.

① Use for...of to get the orders one at a time and print each in the form "A-1001: Delivered".

② Loop again, this time getting the position as well, and print only the orders that aren't delivered, in the form "#2 A-1002: In transit".

③ Count how many you printed in ②, and at the end print "Not delivered: ◯".

JavaScript / TypeScript Editor

Run code to see output

Walking Through Keys — for...in, and Why Not to Use It on Arrays

Sometimes you want to print both the key names and the values side by side, as with a member profile. You could turn the keys into an array with Object.keys(profile) and loop over it with for...of, but if all you need is to walk through the keys, there's a shorter way.

for...in (a statement that goes through an object's keys one at a time) is written as for (const key in profile). The variable holds the key as a string, not the value, so you read the value with bracket notation, as in profile[key].

const profile = { displayName: "Alice", plan: "premium", posts: 12 };

// key holds the key as a string (not the value)
for (const key in profile) {
  console.log(`${key}: ${profile[key]}`);
}
// displayName: Alice
// plan: premium
// posts: 12

// On an array, the indexes come through as strings
const stockCounts = [5, 0, 12];
for (const key in stockCounts) {
  console.log(`${typeof key} / ${key + 1}`);
}
// string / 01
// string / 11
// string / 21   ← joined as strings, not added as numbers
for...in Hands You Keys
Objectprofilekey is thekey stringRead the valuewith profile[key]plan: premiumArraystockCountskey is"0" "1" "2"key + 1 joinsstringsBecomes "01"
With an object you get each key as a string, and with an array you get each index as a string. Adding 1 on an array doesn't do arithmetic; it joins strings.

If you're only reading elements, as in stockCounts[key], the string "1" still gets you the value. You could convert it with Number(key) to use it in calculations, but for array elements it's simpler to choose a different loop from the start. The table below shows when to use each of the three.

SyntaxWhat goes into the variableBest for
for (let i = 0; i < count; i++)The index as a numberUsing the index in calculations
for (const item of array)The element itselfProcessing each element in order
for (const key in object)The key as a stringWalking through an object's keys

You Can't Loop Over an Object with for...of

If you put an object to the right of of, as in for (const item of profile), it stops with TypeError: profile is not iterable. Plain objects aren't iterable: they don't define a way to hand over their contents one at a time. To go through the values too, turn the object into an array with Object.entries first.

On a profile edit screen, compare the profile before and after saving, and print only the fields that changed. before (the old values) and after (the new values) are already declared.

① Set up a variable, starting at 0, to count the changed fields.

② Use for...in to go through the keys of before one at a time.

③ Read the new value with the same key, and for fields whose values differ, print them in the form "city: Yokohama -> Kawasaki" and count them.

④ At the end, print "◯ fields changed".

JavaScript / TypeScript Editor

Run code to see output

Listing Every Combination — Inner and Outer Loops

Say you want to list which products each store carries, as every store-and-product combination. A single loop can step through either the stores or the products, but not both at once. And both the number of stores and the number of products depend on the data.

You can put another loop directly inside a loop's body. The inner loop runs all the way through during each pass of the outer loop, so the number of times the inner body runs is the outer count multiplied by the inner count. The inner loop can also read the outer loop's variable directly.

const stores = ["Shibuya", "Yokohama"];
const products = ["Mug", "Notebook"];

// During each pass of the outer loop, the inner loop runs all the way through
for (const store of stores) {
  for (const product of products) {
    console.log(`${store} / ${product}`);
  }
}
// Shibuya / Mug
// Shibuya / Notebook
// Yokohama / Mug
// Yokohama / Notebook
Variables the Inner Loop Can Read
Outer for...of (one pass per store)
  • store — changes from "Shibuya" to "Yokohama" on each pass
  • The outer body runs only 2 times
Inner for...of (all the way through products)
  • product — changes from "Mug" to "Notebook" on each inner pass
  • Can read the outer store directly
  • This body runs 2 × 2 = 4 times
The inner body can read both the inner product and the outer store. product, declared in the inner loop, can only be used inside the inner curly braces.

The inner for...of starts again from the beginning of products every time the outer pass changes, so while store stays the same, only product changes. A variable declared at the top of the outer body is also created fresh on each outer pass. How curly braces limit where a variable can be used is covered in the article on scope.

Add up the line-item prices for each order. The array orders is already declared; each order has an order number (id) and an array of line-item prices (prices).

① Use an outer loop to go through the orders one at a time.

② Set up a subtotal variable for each order so that it starts from 0 every time the order changes.

③ Use an inner loop to go through that order's prices and add each one to the subtotal.

④ After the inner loop, print a line in the form "A-1001: qty 2 / 1760 yen".

JavaScript / TypeScript Editor

Run code to see output
QUIZ

Knowledge Check

Answer each question one by one.

Q1When you loop over orderIds.entries() with for...of, what is index on the first pass?

Q2When you loop over an array with for...in, what goes into the loop variable?

Q3If you loop over a 3-element array and a 4-element array in a nested loop, how many times does the inner body run?