ZetCode

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.

for_loop.ts
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.

for_of_loop.ts
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.

for_in.ts
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.

while_loop.ts
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.

do_while_loop.ts
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.

nested_loops.ts
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.

break_continue.ts
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.

labeled.ts
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.

foreach.ts
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.

async_loop.ts
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

Source

MDN: Loops and iteration

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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all TypeScript tutorials.