Menu

TypeScript for Loop, forEach, for...of and for...in

Every way to loop in TypeScript and the types each one gives you: the classic for loop, for...of over arrays, maps and strings, for...in and its string keys, forEach (no break, no await), typed loops over object keys, and while.

This page includes runnable editors - edit, run, and see output instantly.

TypeScript has the same loops as JavaScript: for, for...of, for...in, while, do...while, and the array method forEach. What TypeScript adds is the types: the loop variable of a for...of has the element type, and a for...in key is always a string.

For most array loops, for...of is the one to reach for: it reads cleanly, supports break, continue and await, and needs no annotations.

The Classic for Loop

for (init; condition; update) is the loop for counting, stepping by more than one, or walking backwards. TypeScript infers the counter from its initial value.

Declare the counter with let, not var. With let, each iteration gets its own binding, so a callback created in the loop sees that iteration's value.

for...of: Values of Arrays, Strings and Maps

for...of works on anything iterable, and the loop variable gets the matching type. To get the index as well, loop over array.entries(), which yields [index, value] tuples.

break leaves the loop and continue skips to the next item, exactly as in a for loop. The runtime details (iterables, generators) are on the JavaScript for...of and for...in page.

forEach: No break, No await

forEach calls a function for each element and returns undefined. The callback's parameters are typed from the array, so no annotations are needed. Two things it cannot do:

  • Stop early. break inside the callback is compile error TS1107, Jump target cannot cross function boundary. A return only ends the current call, so it acts like continue.
  • Wait for async work. forEach ignores the promise an async callback returns.

The async pitfall is easy to miss, because it compiles without a warning:

The forEach callbacks finish in timer order (10, 20, 30) after the loop has already moved on. The for...of version runs them in sequence. For parallel work that you still wait for, use await Promise.all(items.map(async (item) => { ... })).

for...in: Keys Are Strings

for...in loops over the enumerable property names of an object. The key's type is always string, even for arrays, where the keys are the indexes as strings. That makes it awkward for indexing a typed object:

index.ts(5,22): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ name: string; age: number; }'.
  No index signature with a parameter of type 'string' was found on type '{ name: string; age: number; }'.

TypeScript types the key as string on purpose: at runtime an object can have more properties than its type lists (structural typing allows extra ones), and for...in also walks inherited enumerable properties. So it cannot promise the key is "name" | "age". The fixes are in the next section. Avoid for...in on arrays altogether.

Looping Over an Object's Keys and Values

Object.entries gives [key, value] pairs and is the usual way to loop over an object. Object.keys returns string[] for the same reason for...in does, so when you know the object has no extra keys, assert the key type.

For data whose keys are not known in advance, type the object as a Record<string, number> or use a Map; both are covered on the dictionary page.

while and do...while

while checks its condition before each pass; do...while runs the body once before checking. They suit loops that do not walk a collection, such as reading until a condition holds. Narrowing works inside them: after a !== undefined check in the condition, the variable is narrowed in the body.

Labels, break and continue in Nested Loops

A plain break leaves only the innermost loop. To leave an outer loop, label it and name the label:

Which Loop to Use

GoalLoop
Every element of an arrayfor (const x of arr)
Element and indexfor (const [i, x] of arr.entries()) or arr.forEach((x, i) => ...)
Stop earlyfor...of with break, or find, some, every
Await each stepfor...of with await inside
Build a new arraymap, filter (not a loop)
Count or step by nfor (let i = 0; ...; i += n)
Object keys and valuesfor (const [k, v] of Object.entries(obj))
Map entriesfor (const [k, v] of map)
Until a conditionwhile

Frequently Asked Questions

How do you write a for loop in TypeScript?

The same as in JavaScript: for (let i = 0; i < 5; i++) { ... }; TypeScript infers i as number. To go over the elements of an array, for (const item of items) is shorter, and item gets the array's element type.

How do I break out of forEach in TypeScript?

You cannot: break inside the callback is compile error TS1107 (Jump target cannot cross function boundary), and return only ends the current callback call, like continue. Use for...of with break, or some/find/every, which stop early by design.

What is the difference between for...of and for...in in TypeScript?

for...of visits the values of an iterable (array elements, string characters, Map entries) with their real types. for...in visits the enumerable property keys of an object, always typed string, including array indexes as strings. Use for...of for arrays and Object.entries for objects.

How do I loop through an object's keys and values in TypeScript?

Use for (const [key, value] of Object.entries(obj)). key is a string and value is the union of the property types. If you need key typed as keyof typeof obj, assert it: (Object.keys(obj) as (keyof typeof obj)[]), since TypeScript deliberately types Object.keys as string[].

Can you use await inside forEach?

It compiles, but forEach does not wait for the promises the callback returns, so the loop finishes before the work does. Use for...of with await inside for sequential work, or await Promise.all(items.map(async (x) => ...)) to run it in parallel.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED