TypeScript Loops
last modified October 15, 2026
A loop executes a block of code repeatedly, either a fixed number of times, once for every element of a collection, or as long as a condition holds. TypeScript has the same loops as JavaScript. Its contribution is types: the compiler infers the type of the loop variable from the collection being iterated, so the body of the loop is checked like any other code.
For Loop
The classic for loop has three parts: an
initialization, a condition, and an update expression.
const numbers: number[] = [3, 8, 2, 7, 5];
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
console.log(`index ${i}: ${numbers[i]}, running sum ${sum}`);
}
for (let i = numbers.length - 1; i >= 0; i -= 2) {
console.log(`backwards: ${numbers[i]}`);
}
The variable i starts at 0, the body runs while
i is less than the length of the array, and i++
increases it after each pass. The loop gives access to the index, which is
useful when the position matters. The second loop walks the array backwards
with a step of 2, which shows that all three parts can be any
expressions.
$ npx tsx for_loop.ts index 0: 3, running sum 3 index 1: 8, running sum 11 index 2: 2, running sum 13 index 3: 7, running sum 20 index 4: 5, running sum 25 backwards: 5 backwards: 2 backwards: 3
For-Of Loop
The for...of loop iterates over the values of an
iterable: an array, a string, a map, a set, or a generator.
const fruits: string[] = ["Apple", "Banana", "Cherry"];
for (const fruit of fruits) {
console.log(fruit.toUpperCase());
}
for (const [index, fruit] of fruits.entries()) {
console.log(`${index + 1}. ${fruit}`);
}
for (const char of "hey") {
console.log(char);
}
TypeScript infers fruit as string from the
array type, so string methods are available. The loop variable is declared
with const, because each iteration gets a new binding. The
entries method yields [index, value] pairs when the
index is needed too. for...of is the preferred way to visit every
element of a collection.
$ npx tsx for_of_loop.ts APPLE BANANA CHERRY 1. Apple 2. Banana 3. Cherry h e y
For-In Loop
The for...in loop iterates over the property names of
an object.
const scores = { math: 92, physics: 85, chemistry: 78 };
for (const subject in scores) {
console.log(subject, scores[subject as keyof typeof scores]);
}
for (const [subject, score] of Object.entries(scores)) {
console.log(`${subject}: ${score}`);
}
const letters = ["a", "b"];
for (const key in letters) {
console.log(key, typeof key);
}
The loop variable of for...in always has the type
string. To index the object with it, the key must be asserted as
keyof typeof scores, because TypeScript cannot be sure that the
object has no other properties. Object.entries with
for...of avoids the assertion and gives the value directly.
With arrays, for...in yields the indexes as strings,
"0" and "1", not the elements; that is why it
should not be used for arrays.
$ npx tsx for_in.ts math 92 physics 85 chemistry 78 math: 92 physics: 85 chemistry: 78 0 string 1 string
While Loop
A while loop runs as long as its condition is true. It
is used when the number of iterations is not known in advance.
let amount = 1000;
let years = 0;
while (amount < 2000) {
amount *= 1.07;
years++;
}
console.log(`doubled after ${years} years: ${amount.toFixed(2)}`);
The loop computes how many years an amount needs to double at 7% interest. The condition is checked before each iteration; the body must eventually make it false, or the loop never ends.
$ npx tsx while_loop.ts doubled after 11 years: 2104.85
Do-While Loop
A do...while loop checks its condition after the
body, so the body always runs at least once.
let count = 10;
while (count < 5) {
console.log(`while: ${count}`);
count++;
}
do {
console.log(`do-while: ${count}`);
count++;
} while (count < 5);
console.log(count);
The condition count < 5 is false from the start.
The while loop therefore does not run at all, but the
do...while loop runs its body once before checking the
condition. This fits tasks such as asking for input until it is valid, where
the first attempt always happens.
$ npx tsx do_while_loop.ts do-while: 10 11
Nested Loops
A loop can contain other loops, for example to process a grid or a table.
const size = 4;
for (let row = 1; row <= size; row++) {
let line = "";
for (let col = 1; col <= size; col++) {
line += String(row * col).padStart(4);
}
console.log(line);
}
The outer loop goes over the rows, the inner loop over the columns of each row, and together they print a multiplication table. The inner loop runs completely for every iteration of the outer loop, so the body executes 16 times.
$ npx tsx nested_loops.ts 1 2 3 4 2 4 6 8 3 6 9 12 4 8 12 16
Break and Continue
The break statement ends a loop early; the
continue statement skips the rest of the current
iteration.
for (let i = 0; i < 10; i++) {
if (i === 7) {
break;
}
if (i % 2 === 0) {
continue;
}
console.log(i);
}
const orders = [120, 45, 300, 80];
let firstLarge: number | undefined;
for (const order of orders) {
if (order > 200) {
firstLarge = order;
break;
}
}
console.log(firstLarge);
In the first loop, continue skips the even numbers and
break stops the loop when i reaches 7, so only 1,
3, and 5 are printed. The second loop stops at the first order larger than
200. The variable is declared as number | undefined, because the
loop might not find any such order.
$ npx tsx break_continue.ts 1 3 5 300
Labeled Statements
A plain break exits only the innermost loop. A label
lets it exit an outer loop.
const grid = [
[1, 4, 7],
[2, 9, 3],
[8, 5, 6],
];
let found: [number, number] | null = null;
search: for (let r = 0; r < grid.length; r++) {
for (let c = 0; c < grid[r].length; c++) {
if (grid[r][c] === 9) {
found = [r, c];
break search;
}
}
}
console.log(found);
The label search: names the outer loop, and
break search ends both loops as soon as the value is found.
Without the label, the outer loop would continue with the next row. Labels
are rare; moving the search into a function and using return is
often clearer.
$ npx tsx labeled.ts [ 1, 1 ]
forEach and Array Methods
Arrays also have methods that take a callback and call it for every element.
const prices = [12.5, 8, 3.25];
prices.forEach((price, index) => {
console.log(`item ${index}: ${price.toFixed(2)}`);
});
const total = prices.reduce((sum, price) => sum + price, 0);
const expensive = prices.filter(price => price > 5);
console.log(total, expensive);
forEach passes the element and its index to the
callback. Unlike a loop, it cannot be stopped with break, and
return only ends the current callback. When the goal is to
compute a value, methods such as reduce, filter,
and map express it more directly than a loop. See the
arrays tutorial.
$ npx tsx foreach.ts item 0: 12.50 item 1: 8.00 item 2: 3.25 23.75 [ 12.5, 8 ]
Loops with await
Inside an async function, a loop can wait for an
asynchronous operation in each iteration.
function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function download(file: string): Promise<string> {
await delay(100);
return `${file} done`;
}
async function main(): Promise<void> {
const files = ["a.txt", "b.txt", "c.txt"];
let start = Date.now();
for (const file of files) {
console.log(await download(file));
}
console.log(`sequential: ~${Math.round((Date.now() - start) / 100) * 100} ms`);
start = Date.now();
const results = await Promise.all(files.map(download));
console.log(results, `parallel: ~${Math.round((Date.now() - start) / 100) * 100} ms`);
}
main();
await in a for...of loop runs the
downloads one after another, which takes about three times as long as one
download. When the operations are independent, Promise.all
starts them all at once. Note that await does not work as
expected inside a forEach callback, because
forEach does not wait for the returned promises. See the
async/await tutorial.
$ npx tsx async_loop.ts a.txt done b.txt done c.txt done sequential: ~300 ms [ 'a.txt done', 'b.txt done', 'c.txt done' ] parallel: ~100 ms
Best Practices
- Prefer for...of for Collections: It is short, works with any iterable, and gives typed values.
- Use for...in Only for Objects: For arrays, it yields string indexes;
Object.entriesis often better even for objects. - Make Sure Loops End: The body of a
whileloop must eventually make its condition false. - Use Array Methods for Transformations:
map,filter, andreducestate the intent more clearly than a manual loop. - Avoid await in forEach: Use
for...offor sequential work andPromise.allfor parallel work.
Source
This tutorial covered loops in TypeScript. They are the loops of JavaScript; TypeScript adds inferred types for the loop variables and checks how they are used.
Author
List all TypeScript tutorials.