Loops: while, do-while, for
In the very first lesson, to output three lines, you had to write nearly identical std::cout three times. For three lines that's tolerable, for a thousand it isn't. Loops solve exactly this: run the same code many times without copying it by hand.
01What you'll learn
while— repeat while a condition is true, checked before each repetitiondo-while— the same, but checked after, at least one execution guaranteedfor— a compact form when you know how many times to repeatbreak— exit the loop entirely before its normal endcontinue— skip the rest of the current repetition and move to the next one
02The core idea: "repeat while..."
A loop is the computer equivalent of an instruction like "repeat this until a condition is met," exactly as in everyday tasks: wash dishes while there are dirty ones in the sink; do 20 push-ups; keep checking email until the message you need arrives. Each of these examples has an action that repeats and a condition that eventually stops the repetition. The three constructs in this lesson — while, do-while, for — do exactly this, differing only in when the condition is checked and how compactly the repetition counter is written.
03while — while the condition is true
A countdown from 3 to Liftoff is three lines that differ only in the number. Writing them by hand isn't hard, but if the countdown starts not at 3 but at 100, it is. while takes the same bool condition you've already seen in if and simply doesn't release execution from the { } block while the condition remains true — it re-checks it before each repetition.
int countdown = 3;
while (countdown > 0)
{
std::cout << countdown << "\n";
countdown = countdown - 1;
}
std::cout << "Liftoff!\n";
Important: countdown = countdown - 1; inside the block is what eventually makes the condition false. Without this line, the condition countdown > 0 stays true forever — the program loops endlessly and never finishes (see "Common Mistakes"). The condition is checked before each repetition — if it's false from the very beginning, the block never executes.
04do-while — execute first, then check
do-while is the same, but the condition is checked after the block, not before. Because of this, the loop body executes at least once, even if the condition is initially false:
int attempts = 0;
do
{
std::cout << "Attempt\n";
attempts = attempts + 1;
}
while (attempts < 3);
Note the syntax: while (...) here comes after the block and ends with ; — it's easy to get this wrong, confusing it with a regular while. In practice, do-while is used less often than a regular while — only where the logic requires performing the action at least once before checking the condition for the first time (for example: show a menu at least once, even if the user immediately chooses "exit").
05for — a known number of repetitions
"Output numbers from 1 to 5" using while requires a counter variable before the loop, a condition in the while parentheses, and a step changing the counter somewhere inside the block — three logically related parts spread across three different places in the code. for exists precisely so that for the most common case ("it's known in advance how many times to repeat") these three parts don't have to be separated — it gathers them into one line:
for (int i = 1; i <= 5; i++)
{
std::cout << i << "\n";
}
Three parts in the parentheses, separated by ;: int i = 1 — initialization, runs once before the first repetition; i <= 5 — condition, checked before each repetition, like in while; i++ — step, executed after each repetition (i++ is shorthand for i = i + 1, increment). The same loop as above, written with while, would look like this:
int i = 1;
while (i <= 5)
{
std::cout << i << "\n";
i++;
}
The logic is identical — for simply keeps all three parts together, which reduces the risk of forgetting the counter-changing step (a common cause of infinite loops, see below).
06while, do-while and for — table
while | do-while | for | |
|---|---|---|---|
| When the condition is checked | before each repetition | after each repetition | before each repetition (like while) |
| Minimum body executions | 0 (if the condition is immediately false) | 1 (the body always executes at least once) | 0 (if the condition is immediately false) |
| Where the counter lives | declared on a separate line before the loop | declared on a separate line before the loop | initialization, condition and step — all three parts on one line |
| When it's more convenient | the number of repetitions is unknown in advance (e.g., "until the correct password is entered") | the action must be performed at least once before the first check (e.g., showing a menu) | the number of repetitions is known in advance (e.g., "exactly 10 times" or "over all elements") |
| Example | while (x > 0) { ...; x--; } | do { ...; x++; } while (x < 3); | for (int i = 0; i < 5; i++) { ... } |
In practice, for is chosen most often precisely because the counter, condition and step are visible in one place — it's harder to forget one of them, unlike while, where these three parts are scattered across different lines (see "Live Debugging" below about the forgotten step).
07Nested loops
A loop body is a { } block, and inside a block there can be any instruction, including another loop — exactly the same nesting principle you saw with if inside if in the previous lesson. The example program illustrates this well: a squad of several members, and for each one several combat rounds must be simulated — the outer loop iterates over squad members, the inner loop over combat rounds for the current member.
int damage = 5;
for (int member = 1; member <= 3; member++)
{
int hp = member * 20; // each squad member has their own starting HP
for (int round = 1; round <= 3; round++)
{
hp = hp - damage;
std::cout << "Member " << member << ", round " << round << ": HP = " << hp << "\n";
}
}
On each repetition of the outer loop (member), the variable hp is declared anew — with its own starting value for that member — and the inner loop (round) executes completely, from start to finish, before the outer loop moves to the next member. That's exactly why the second member’s hp starts at 40 rather than continuing to decrease from the first — it's declared inside the outer loop's body, so it's created anew on each of its repetitions (the same scope rules as in the previous lesson). For three members with three rounds each, the inner loop body executes 3 × 3 = 9 times — as nested loops grow, the number of repetitions grows as a product, not a sum, which is worth keeping in mind.
08Worse → better: loop boundaries
The task is to output numbers from 1 to 5 inclusive, exactly five of them. All three versions below are syntactically correct and compile without a single error.
for (int i = 0; i <= 5; i++)
{
std::cout << i << " ";
}
The counter starts at 0 (but it should start at 1), and the condition i <= 5 includes the number 5 itself — six repetitions total instead of five. A classic off-by-one, covered below in "Common Mistakes."
for (int i = 1; i <= 5; i++)
{
std::cout << i << " ";
}
Correct in count and values — but the output ends with a trailing space after the last number (invisible in the console, but not ideal for clean formatting).
for (int i = 1; i <= 5; i++)
{
std::cout << i;
if (i < 5)
{
std::cout << ", ";
}
}
std::cout << "\n";
if (i < 5) inside the loop — from the previous lesson: the separator ", " is printed only between numbers, not after the last one. A small detail, but that's exactly how lists are formatted in real output.
09break and continue
Both work inside any loop (while, do-while, for) and change its normal flow:
| Operator | What it does |
|---|---|
break; | immediately ends the entire loop — execution continues right after it, as if the condition had already become false |
continue; | skips the remaining part of the current repetition and moves to the next condition check (in for — the step runs first) |
for (int i = 1; i <= 10; i++)
{
if (i == 5)
{
break; // the loop stops at i == 5, won't go further
}
std::cout << i << "\n"; // outputs 1 2 3 4
}
break in switch from the previous lesson is the same operator: it "exits" the surrounding construct in exactly the same way — there the surrounding construct was switch, not a loop.
10Try changing it
- In the example from the "for" section, replace
i <= 5withi < 5— the last number in the output changes from 5 to 4. - In the example from the "break and continue" section, replace
break;withcontinue;— instead of stopping at 5, the loop will only skip outputting the number 5 and continue to 10. - Remove
attempts = attempts + 1;from thedo-whileexample — the program will loop endlessly (don't do this in a real project without a way to interrupt execution). - In the squad example, replace the inner limit
round <= 3withround <= 5— each member will take five rounds of damage instead of three.
11Live debugging: common mistakes
As in the previous two lessons, none of these mistakes will stop compilation.
int i = 0;
while (i < 5)
{
std::cout << i; // i never changes — the condition is always true
}
In for, this mistake is rarer precisely because the step is written right next to the condition and is harder to forget.
for (int i = 0; i < 5; i++); // the ; here is an empty loop body
{
std::cout << i;
}
; right after ) is an empty statement (an expression statement with no content, see the second lesson), and that's what becomes the loop body; the loop executes "nothing" five times, and the { } block below executes once after the loop, with i equal to 5. The compiler doesn't consider this an error — completely valid syntax, just not what was intended.
The full breakdown with output example is in the "Worse → better" section above. A useful habit: for "exactly N repetitions, starting from 0" — i < N; for "including the last value N" — i <= N, usually starting from 1.
12Check yourself
Three questions: predicting the number of repetitions, diagnosing an infinite loop, and the difference between break and continue.
for (int i = 2; i <= 8; i = i + 2)
{
std::cout << i << " ";
}Show explanation
Four times, outputting 2 4 6 8. The step here is not i++ but i = i + 2 — the counter increases by 2 per repetition, not by 1; the loop still stops when the condition i <= 8 becomes false (at i = 10).
int lives = 3;
while (lives > 0)
{
std::cout << "Try again\n";
}Show explanation
Inside the block, lives never changes — the condition lives > 0 stays true forever (see §11). A statement like lives = lives - 1; is needed inside the block, one that will eventually make the condition false.
Show explanation
break — the loop must end entirely, subsequent numbers no longer matter. For comparison, the task "go through all numbers from 1 to 20 and output all except multiples of 3" is solved with continue — the loop must run to the end, but some repetitions are simply skipped without output rather than interrupting the entire iteration.
13Practice
★ Output numbers from 1 to 5 inclusive, each on a new line, using for.
Three parts in the for parentheses: initial value of i, stop condition, step — exactly as in section 05.
Solution: for (int i = 1; i <= 5; i++) { std::cout << i << "\n"; }
★★ Output only the even numbers from 1 to 10 inclusive: iterate through all numbers with a for loop, and skip the odd ones with continue (use % to check evenness).
The odd remainder of division by 2 is 1, not 0 — so the condition for skipping is: i % 2 != 0. After continue, the code below it in this repetition won't execute.
Solution: if (i % 2 != 0) { continue; } std::cout << i << "\n";
★★★ Simulate combat for a squad of 3 members using nested for loops, as in the "Nested loops" section above: outer loop — members (member from 1 to 3, starting HP = member * 20), inner loop — 3 rounds of damage (damage = 5 per round), output each member’s HP after each round.
The outer for handles members — hp is declared ANEW inside its body on each repetition, with its own starting value of member * 20.
The inner for — entirely inside the outer loop's body — reduces hp by damage on each of its own repetitions and outputs the current value.
Solution: for (int member = 1; member <= 3; member++) { int hp = member * 20; for (int round = 1; round <= 3; round++) { hp = hp - damage; std::cout << "Member " << member << ", round " << round << ": HP = " << hp << "\n"; } }
14Terms
| Term | Meaning |
|---|---|
| Loop | a construct that repeats a block of code while a condition holds |
| Iteration | one repetition of the loop body |
| Loop counter | a variable tracking the current repetition number (e.g., i in for) |
| Off-by-one | an error of one iteration too many or too few due to an incorrect condition boundary |
| Nested loop | a loop whose body contains another loop |
15What you can do now
Before this lesson, every repeated line of code was written by hand. Now repetition is a single construct:
- You can choose between
while,do-whileandforfor a specific task — whether the number of repetitions is known in advance and whether at least one execution must be guaranteed. - You can predict how many times a loop body will execute and with what counter values, including a non-standard step.
- You can find the cause of an infinite loop in someone else's code — the absence of a change to the variable affecting the condition.
- You can use
breakandcontinuedeliberately, understanding the difference between "stop the entire loop" and "skip one repetition." - You can nest one loop inside another and explain why the number of inner-body repetitions grows as a product, not a sum.
16Summary
while (condition) { ... }— repeats the block while the condition is true; checked before each repetition.do { ... } while (condition);— the same, but checked after — at least one execution is guaranteed.for (initialization; condition; step) { ... }— a compact form for a known number of repetitions.break;— exit the loop (or switch) entirely;continue;— skip the rest of the current repetition.- A forgotten counter-changing step is the most common cause of a hung program.
Loops repeat the same code — but for now that code must be written directly inside main(). The next lesson is functions: a way to extract repetitive logic into a separate named block and call it from anywhere, not just inside a single loop.
while/for work in Unreal C++ unchanged — you'll iterate over an inventory's item array or the list of players in a room exactly this way:
for (int32 i = 0; i < InventoryItems.Num(); i++)
{
UE_LOG(LogTemp, Log, TEXT("Item %d: %s"), i, *InventoryItems[i]);
}
// or, more idiomatic for Unreal arrays (TArray) — range-based for:
for (const FString& Item : InventoryItems)
{
UE_LOG(LogTemp, Log, TEXT("Item: %s"), *Item);
}
A separate parallel will appear later: every Actor in Unreal has a Tick(float DeltaTime) function that the engine itself calls every frame — essentially, it's an "external" loop that you don't write by hand (the condition "while the game is running" and the repetition are hidden inside the engine), but you write the code that runs on each of its repetitions:
void AMyActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// this code runs every frame — like a loop body without a visible while
Health = FMath::Clamp(Health + RegenRate * DeltaTime, 0.0f, MaxHealth);
}
TArray.Num() instead of array length and Tick() — Unreal-specific details, covered in Stage 07+; the for/while loops themselves inside are the same as in this lesson. One more detail that would otherwise remain a blank spot: %d and %s inside TEXT("...") are not something new in the language — it's the old printf-style string formatting syntax that UE_LOG uses instead of the familiar std::cout <<; %d means "put an integer here," %s means "put a string here," and the order of substitution is set by the arguments after the string itself, left to right. The full breakdown of this format is in Stage 07+; here it's only important not to confuse it with std::cout and not to try to write UE_LOG the same way as regular output.