TypeScript Loop Fundamentals: while, do-while, for, break and continue
Learn how TypeScript's while, do-while and for loops evaluate their conditions, why loop variable scope changes results, and how break and continue alter the flow.
This article is published in English.
Sources: Mastering Loops in TypeScript: while, do-while, for, break & continue
Loops let you run the same block of code repeatedly while a condition holds, instead of copying a statement over and over. They look simple, but most loop bugs come from three details: when the condition is checked, where the loop variable lives, and where a break or continue sits in the body. This guide walks through TypeScript's while, do-while and for loops with small examples whose output you can predict line by line, so you can read and write iteration code without surprises.
Why iteration beats repetition
Printing 1 through 10 without a loop takes ten console.log calls. With a loop, you describe the work once and the runtime repeats it, and the code stays the same size whether you need ten repetitions or ten thousand.
The while loop: check first, then run
A while loop has the simplest shape of all:
while (condition) {
// statements
}
The condition is evaluated before every pass. While it is true, the body runs; once it is false, the loop ends. A working while loop needs three ingredients:
- an initial value for the variable the condition depends on;
- the condition itself;
- something in the body that moves that variable toward making the condition
false.
Leave out the third ingredient and the loop never stops, a problem covered below.
Counting from 1 to 10
Here all three ingredients are visible: i starts at 1, the loop runs while i <= 10, and i++ advances it on every pass:
let i: number = 1; // initialization
while (i <= 10) {
console.log(i);
i++;
}
Filtering for even numbers
To print only the even numbers in that range, keep visiting every value but add a check inside the body. The modulo operator % returns the remainder of a division, so i % 2 == 0 is true exactly when i is even:
let i: number = 1;
while (i <= 10) {
if (i % 2 == 0) {
console.log(i);
}
i++;
}
There is a leaner route to a similar result: skip the check entirely and advance by two each time. Note that this version starts at 0, so its output includes 0 as well:
let i: number = 0;
while (i <= 10) {
console.log(i);
i = i + 2;
}
Output: 0, 2, 4, 6, 8, 10
Filtering for odd numbers
Flipping the comparison to != 0 selects the odd values instead. Starting from 0 is harmless here, because 0 is even and gets filtered out:
let i: number = 0;
while (i <= 10) {
if (i % 2 != 0) {
console.log(i);
}
i++;
}
Output: 1, 3, 5, 7, 9
These examples use == and !=; the strict forms === and !== are generally preferred because they never coerce types, although with two numbers the result is identical.
Counting down
To count backwards, start high and decrement while the value is at least 1:
let i: number = 10;
while (i >= 1) {
console.log(i);
i--;
}
Infinite loops and how they happen
A while condition is just an expression that evaluates to a boolean, so while (true) is perfectly valid. The loop below prints forever because nothing ever ends it:
let i: number = 1;
while (true) {
console.log(i);
i++;
// no break, no exit condition this runs forever
}
More often, infinite loops are accidental: the controlling variable is never updated, or moves the wrong way. A runaway loop blocks everything else on its thread, so a browser tab or Node.js process appears to hang. A deliberate while (true) is fine when paired with an explicit exit, usually break (covered below), for example in "retry until it succeeds" logic.
The do-while loop: run once, then check
A do-while loop moves the condition to the end:
do {
// statements
} while (condition);
Because the check happens after the body, the body always runs at least once, whatever the condition says. A plain while, by contrast, may run zero times.
When the condition starts out true, a do-while behaves just like a while:
let i: number = 1;
do {
console.log(i);
i++;
} while (i <= 5);
The difference shows up when the condition is false from the start:
let i: number = 10;
do {
console.log(i);
i++;
} while (i <= 5);
Output: 10
The body runs unconditionally, printing 10 and bumping i to 11. Only then is i <= 5 evaluated; it is false, so the loop exits after one iteration. A while (i <= 5) starting from 10 would print nothing. Use do-while when the first run is required regardless, such as showing a prompt at least once.
The for loop: everything in one header
The for loop is the one you will use most, particularly when you know in advance how many times to repeat or by how much the counter should change. It gathers initialization, condition and update into a single header, which makes it more compact than the equivalent while:
for (initialization; condition; increment/decrement) {
// statements
}
The order in which those parts run is worth memorising:
- The initialization runs once, before anything else (for example
let i = 1). - The condition is evaluated. If it is
true, the body runs; iffalse, the loop ends. - After the body, the update expression runs (for example
i++). - Control returns to step 2, and the cycle repeats until the condition is
false.
In other words, initialization happens a single time, while the condition and the update happen on every cycle.
Counting from 1 to 10
for (let i = 1; i <= 10; i++) {
console.log(i);
}
Two ways to get even numbers
As with while, you can either step by two:
for (let i = 2; i <= 10; i += 2) {
console.log(i);
}
or visit every value and filter inside the body:
for (let i = 2; i <= 10; i++) {
if (i % 2 == 0) {
console.log(i);
}
}
Both print the same sequence:
2
4
6
8
10
Stepping does less work because it only visits even values; filtering is more flexible when the rule is not a fixed step, such as "divisible by 3 or 7".
Counting down
A decrementing update runs the loop in reverse:
for (let i = 10; i >= 1; i--) {
console.log(i);
}
Output: 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
Why the loop variable is 6 after the loop
Where you declare the loop variable changes what exists after the loop. In this example, i is declared before the loop, and the for header only assigns to it:
let i: number;
for (i = 1; i <= 5; i++) {
console.log(i);
}
console.log(i);
Output: 1, 2, 3, 4, 5, 6
Because i lives in the outer scope, the final console.log(i) still sees it, holding whatever the loop left behind. That is 6, not 5, because of the order of operations above: on the last pass i is 5, the body prints it, the update raises it to 6, and only then does 6 <= 5 fail and end the loop.
The more common style, for (let i = 1; i <= 5; i++), gives i block scope limited to the loop, so it no longer exists afterwards. Referring to it after the loop is a compile-time error in TypeScript ("Cannot find name 'i'"), and in plain JavaScript it would throw a ReferenceError at runtime. Declaring in the header is usually better; use an outer declaration only when you need the final value.
Changing the flow with break and continue
Two jump statements let you alter a loop's normal flow from inside the body:
breakends the loop immediately, however many iterations were still to come.continueabandons only the current iteration and moves on to the next one; the loop keeps running.
In a for loop, continue still runs the update expression before the condition is checked again, which is why the counter keeps advancing.
break: position inside the body matters
Here the break check comes before the print:
let i: number;
for (i = 1; i <= 10; i++) {
if (i == 5) {
break;
}
console.log(i);
}
Output: 1, 2, 3, 4
When i reaches 5, the loop exits before console.log gets a chance to run, so 5 never appears.
Move the print above the check and the result changes:
let i: number;
for (i = 1; i <= 10; i++) {
console.log(i);
if (i == 5) {
break;
}
}
Output: 1, 2, 3, 4, 5
The condition is unchanged, yet 5 now appears because the print runs before the check. With break and continue alike, statement order inside the body decides the output.
continue: skipping a single value
Replacing break with continue turns "stop at 5" into "skip 5":
let i: number;
for (i = 1; i <= 10; i++) {
if (i == 5) {
continue;
}
console.log(i);
}
Output: 1, 2, 3, 4, 6, 7, 8, 9, 10
The loop carries on; it just misses the print for that one iteration.
Skipping every odd number
Combined with a filter, continue sends every odd value straight to the next iteration:
let i: number;
for (i = 0; i <= 10; i++) {
if (i % 2 != 0) {
continue;
}
console.log(i);
}
Output: 0, 2, 4, 6, 8, 10
Skipping a list of specific values
The condition can be as elaborate as you need. Using the logical OR operator ||, this loop skips 3, 5 and 7:
let i: number;
for (i = 0; i <= 10; i++) {
if (i == 3 || i == 5 || i == 7) {
continue;
}
console.log(i);
}
Output: 0, 1, 2, 4, 6, 8, 9, 10
For a longer skip list, [3, 5, 7].includes(i) or a Set reads better.
Key takeaways
whilechecks its condition before each pass and may run zero times;do-whilechecks afterwards and always runs at least once.- Every loop needs an update that eventually makes the condition false; otherwise you get an infinite loop that blocks the thread.
- In a
forloop, initialization runs once, while the condition and the update run on every cycle, which is why a counter ends one step past the limit. - Declaring the counter with
letin the loop header keeps it scoped to the loop; declaring it outside lets its final value survive. breakends the loop andcontinueskips one iteration; where you place them relative to other statements changes the output.
Conditionals choose which code executes; loops choose how often. Between them, they cover most of the control flow you will write.