TypeScript for Loop: for, for…of, for…in and forEach

The classic for loop, for…of, for…in and forEach() in TypeScript, with break, continue, labels, and iteration over Map, Set and object entries.

angular-typescript

TypeScript has three for loops and one array method for repeating code over a collection. All four come from JavaScript, and TypeScript adds type checking for the loop variables, which catches mistakes such as reading an object with a key of the wrong type.

  • The classic for loop counts with an index.
  • The for…of loop goes through the values of an array, string, Map or Set.
  • The for…in loop goes through the keys of an object.
  • The array method forEach() calls a function once per element.

We use a loop whenever code must handle every item of a collection, for example to add up the prices in a shopping cart or to send a reminder to each user on a list.

The following example shows the loop forms and the break and continue statements that control them, with the printed values as comments. The snippets compile with TypeScript 7.0.2 with strict checks on and run on Node.js 22.

const fruits = ["apple", "banana", "cherry"];

// 1. Classic for: index and value
for (let i = 0; i < fruits.length; i++) {
  console.log(i, fruits[i]);                  // 0 apple, 1 banana, 2 cherry
}

// 2. for...of: values
for (const fruit of fruits) {
  console.log(fruit);                         // apple, banana, cherry
}

// 3. for...of with entries(): index and value
for (const [i, fruit] of fruits.entries()) {
  console.log(i, fruit);                      // 0 apple, 1 banana, 2 cherry
}

// 4. for...in: keys of an object
const ages = { Lokesh: 37, Raj: 35 };
for (const name in ages) {
  console.log(name);                          // Lokesh, Raj
}

// 5. forEach(): one callback call per element, no break
fruits.forEach((fruit, i) => console.log(i, fruit));   // 0 apple, 1 banana, 2 cherry

// 6. break and continue
for (const n of [1, 2, 3, 4, 5]) {
  if (n === 2) continue;                      // skips 2
  if (n === 4) break;                         // stops at 4
  console.log(n);                             // 1, 3
}

// 7. Map, Set and object entries
const prices = new Map([["apple", 5], ["banana", 3]]);
for (const [fruit, price] of prices) {
  console.log(fruit, price);                  // apple 5, banana 3
}
for (const fruit of new Set(["apple", "apple", "kiwi"])) {
  console.log(fruit);                         // apple, kiwi
}
for (const [name, age] of Object.entries(ages)) {
  console.log(name, age);                     // Lokesh 37, Raj 35
}

Notice that for…of gives the values, whereas for…in gives the keys, and that forEach() has no way to stop early.

Each loop gets its own section, including the strict-mode error that the classic for…in example from many tutorials produces and the two ways to fix it. After that, we cover break and continue with labels for nested loops, and looping over a Map, a Set and the entries of an object.

1. The Classic for Loop

The classic for loop has three parts in its parentheses, separated by semicolons.

  • An initializer that runs once
  • A condition checked before every iteration
  • An update that runs after every iteration

The loop stops as soon as the condition becomes false.

for (initializer; condition; update) {
  // body
}

We declare the counter with let, which gives each iteration its own copy of the variable, so a closure created in the loop keeps the value of its own iteration, unlike a counter declared with var. TypeScript infers the counter as number, so no annotation is needed. The classic loop is the right choice when the index itself matters, for example when we count down or when we remove items from the array while looping.

// 1. Count down
for (let i = 3; i > 0; i--) {
  console.log(i);                             // 3, 2, 1
}

// 2. Every second element
const scores = [10, 20, 30, 40, 50];
let sum = 0;
for (let i = 0; i < scores.length; i += 2) {
  sum += scores[i];
}
// sum = 90

// 3. Remove items while looping: go backwards
const nums = [1, 2, 3, 4];
for (let i = nums.length - 1; i >= 0; i--) {
  if (nums[i] % 2 === 0) nums.splice(i, 1);
}
// nums = [1, 3]

The third example walks backwards on purpose, because removing an element with splice() shifts every later element one position to the left. In a forward loop, the element after a removed one moves into the current index, so the loop skips it. Going backwards avoids the problem because only elements already visited shift. A to-do app that deletes all completed tasks from its array is a typical case for the backward loop. When we do not need to change the original array, filtering into a new array with filter() is simpler.

With the noUncheckedIndexedAccess compiler option, scores[i] has the type number | undefined, and the compiler asks us to handle undefined even inside a correct loop. The noUncheckedIndexedAccess check is one more reason to prefer for…of when we do not need the index.

2. The for…of Loop

The for…of loop, added in ES2015, gives us the values of any iterable object, with no counter to manage and no index that can be off by one. Arrays, strings, Map, Set, the result of entries() and generators are all iterable. We declare the loop variable with const, since each iteration gets a new variable. For example, a newsletter job goes through the list of subscribers with for…of and sends one email to each of them.

// 1. Characters of a string
const letters: string[] = [];
for (const ch of "hi!") {
  letters.push(ch);
}
// letters = ["h", "i", "!"]

// 2. Destructuring objects in the loop variable
const people = [{ name: "Lokesh", age: 37 }, { name: "Raj", age: 35 }];
for (const { name, age } of people) {
  console.log(name, age);                     // Lokesh 37, Raj 35
}

// 3. A let loop variable can be reassigned
for (let word of ["apple", "kiwi"]) {
  word = word.toUpperCase();
  console.log(word);                          // APPLE, KIWI
}

Reassigning the loop variable, as in the third example, changes only the variable, not the array element. To change the elements themselves, we use the classic loop with an index or create a new array with map().

When we need the index too, array.entries() returns [index, value] pairs, and the brackets in const [i, fruit] split each pair into two variables. For a string, for…of goes through Unicode characters, so an emoji counts as one character, while str[i] in a classic loop returns half of it.

3. The for…in Loop

The for…in loop goes through the keys of an object as strings, and it is meant for plain objects. A settings page that prints every option of a config object is a typical case. Many tutorials show this loop with person[key] in the body, but reading a value that way does not compile in strict mode.

const ages = { Lokesh: 37, Raj: 35 };
for (const name in ages) {
  console.log(name, ages[name]);
}
src/e.ts(4,23): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ Lokesh: number; Raj: number; }'.
  No index signature with a parameter of type 'string' was found on type '{ Lokesh: number; Raj: number; }'.

TypeScript types the loop variable as string, not as “Lokesh” | “Raj”, because an object at runtime can have more properties than its type lists. A string is not a valid key for the type of ages, so the compiler refuses. We have two fixes.

  • Tell the compiler the key type with as keyof typeof ages.
  • Loop over Object.entries() instead, which returns the keys and values together and needs no assertion.
const ages = { Lokesh: 37, Raj: 35 };

// 1. Read values: tell TypeScript the key type
for (const name in ages) {
  const age = ages[name as keyof typeof ages];
  console.log(name, age);                     // Lokesh 37, Raj 35
}

// 2. Array indexes are strings
const scores = [10, 20];
for (const index in scores) {
  console.log(index, typeof index);           // 0 string, 1 string
}

The second example shows why for…in is a poor fit for arrays, because the index is the string “0”, not the number 0, so arithmetic such as index + 1 produces “01”. The loop also visits every enumerable property, including properties added to the array object or inherited from a modified prototype. For arrays we use for…of or the classic loop. An object typed as Record<string, number> is the exception where for…in with obj[key] compiles, because a TypeScript Record with string keys accepts any string key.

4. The forEach() Method

The array method forEach() calls a function for every element and passes the element, its index and the array. It returns undefined, and it reads well for short side effects, such as logging or pushing into another collection. The Map and Set classes have their own forEach() methods with the same idea.

Because the loop body is a separate function, the loop statements do not work inside it. A break in the callback is a compile error (“error TS1107: Jump target cannot cross function boundary.”), and return only ends the current call, which acts like continue.

const fruits = ["apple", "banana", "cherry"];

// 1. return skips only the current element
const kept: string[] = [];
fruits.forEach((fruit) => {
  if (fruit === "banana") return;
  kept.push(fruit);
});
// kept = ["apple", "cherry"]

// 2. some() stops at the first true result
const visited: string[] = [];
fruits.some((fruit) => {
  visited.push(fruit);
  return fruit === "banana";
});
// visited = ["apple", "banana"]

When the loop must stop early, a for…of loop with break is clearer than some() or every(), and find() fits when we look for one element of an array. The second limit is that forEach() does not wait for async callbacks. Each callback starts, and forEach() returns before any of them finishes. For example, a checkout service that saves each order line with await inside forEach() continues before any line is saved. To process items one after another with await, we use for…of; to run them in parallel, Promise.all() with map().

5. break, continue and Labels

The break statement ends the loop at once, and continue skips the rest of the current iteration and moves on to the next one. Both work in the classic for, for…of, for…in, while and do…while loops, as step 6 of the intro example shows.

A plain break leaves only the innermost loop. To leave two nested loops at once, we put a label before the outer loop and name it in the break. For example, a cinema booking page searches a grid of rows and seats for the first free seat and stops both loops when it finds one.

const grid = [[1, 2], [3, 4], [5, 6]];
let found = "";
outer: for (const [row, cells] of grid.entries()) {
  for (const cell of cells) {
    if (cell === 4) {
      found = "row " + row;
      break outer;
    }
  }
}
// found = "row 1"

Labels are rare in everyday code, and when the nested loops sit in their own function, a return does the same job and is easier to read.

6. Looping Over a Map, a Set and Object Entries

A Map and a Set are iterable, so for…of works on them as they are and returns entries in insertion order. A Map gives [key, value] pairs, and its keys() and values() methods return only one side of each pair. A Set gives its values. A plain object is not iterable, so we first turn it into an array, for example with Object.entries(), which returns [key, value] pairs.

const prices = new Map([["apple", 5], ["banana", 3]]);
const tags = new Set(["new", "sale"]);
const ages: Record<string, number> = { Lokesh: 37, Raj: 35, John: 40 };

// 1. Map: keys(), values(), entries()
const names = [...prices.keys()];             // names = ["apple", "banana"]
let total = 0;
for (const price of prices.values()) {
  total += price;
}
// total = 8

// 2. Set: values in insertion order
for (const tag of tags) {
  console.log(tag);                           // new, sale
}

// 3. Object: keys, values, entries
const people = Object.keys(ages);             // people = ["Lokesh", "Raj", "John"]
const sum = Object.values(ages).reduce((a, b) => a + b, 0);   // sum = 112
for (const [name, age] of Object.entries(ages)) {
  console.log(name, age);                     // Lokesh 37, Raj 35, John 40
}

Writing for…of on the plain object { Lokesh: 37, Raj: 35 } fails with error TS2488: Type ‘{ Lokesh: number; Raj: number; }’ must have a ‘[Symbol.iterator]()’ method that returns an iterator. The forEach() method of a TypeScript Map passes the value before the key, and a TypeScript Set passes the value twice.

7. Choosing the Right Loop

Each loop has a clear use case, and picking the right one removes most loop bugs.

NeedBest choiceWhy
Values of an array, string, Map or Setfor…ofNo index to manage; supports break, continue and await
Index and valuefor…of with entries(), or classic forentries() keeps the code short; the classic loop allows any step
Count down, skip elements, remove while loopingClassic forFull control over the index
Keys of a plain objectObject.entries() with for…of, or for…inObject.entries() gives typed values without an assertion
A short side effect per elementforEach()Compact; no early exit and no await
A new array or a single value from an arraymap(), filter(), reduce()Return a result instead of changing outside variables

Speed is rarely a reason to choose, because all these loops are fast enough for normal data sizes in current engines, so readability matters more.

8. Running the Loop Examples

Each section of this article has its own file in the for-loop examples, next to the quick-reference snippet, and index.ts runs them in order. The compiler is TypeScript 7.0.2 with strict mode turned on, and the runtime is Node.js 22.

npm install
npm start

The output of the for…in part shows the string indexes from section 3.

=== for...in ===
Lokesh 37
Raj 35
0 string
1 string

9. Conclusion

For most loops in TypeScript, for…of is the default. It gives the values of arrays, strings, Map and Set, works with break and continue as well as with await, and adds entries() when the index is needed. The classic for loop is for index arithmetic, and Object.entries() or for…in handle object keys. A forEach() call fits short side effects that never stop early. In strict mode, for…in needs a key type assertion or a switch to Object.entries() before it can read values.

10. References

MDN documents the runtime behavior of every loop, and the TypeScript handbook explains iterables.

Happy Learning !!

Source Code on Github

Leave a Comment

  1. Hi Lokesh

    Great site. Thanks for making this. I have a suggestion. I see let being used in lot of places. Suggest you to use const instead.

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.