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.
breakinside the callback is compile error TS1107,Jump target cannot cross function boundary.Areturnonly ends the current call, so it acts likecontinue. - Wait for async work.
forEachignores the promise anasynccallback 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
| Goal | Loop |
|---|---|
| Every element of an array | for (const x of arr) |
| Element and index | for (const [i, x] of arr.entries()) or arr.forEach((x, i) => ...) |
| Stop early | for...of with break, or find, some, every |
| Await each step | for...of with await inside |
| Build a new array | map, filter (not a loop) |
| Count or step by n | for (let i = 0; ...; i += n) |
| Object keys and values | for (const [k, v] of Object.entries(obj)) |
| Map entries | for (const [k, v] of map) |
| Until a condition | while |
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.