The need for repetition

Consider a game that awards 10 XP at the end of each level. If a player completes 5 levels, you need to add 10 XP five times. Without loops, you'd write:

playerXP += 10;
playerXP += 10;
playerXP += 10;
playerXP += 10;
playerXP += 10;

This works, but what if the player completes 100 levels? Or what if the number of levels varies based on player input? You can't write code for every possibility.

Loops solve this by executing code repeatedly. Instead of duplicating statements, you describe what to repeat and when to stop.

The while loop

The while statement is the simplest loop in C++:

while (condition)
    statement;

The condition is checked before each iteration. If true, the statement executes and control returns to check the condition again. If false, the loop ends and execution continues after it.

Here's the XP example with a loop:

#include <iostream>

int main()
{
    int playerXP{ 0 };
    int levelsCompleted{ 5 };

    int level{ 1 };
    while (level <= levelsCompleted)
    {
        playerXP += 10;
        std::cout << "Level " << level << " complete. XP: " << playerXP << '\n';
        ++level;
    }

    std::cout << "Total XP earned: " << playerXP << '\n';

    return 0;
}

Output:

Level 1 complete. XP: 10
Level 2 complete. XP: 20
Level 3 complete. XP: 30
Level 4 complete. XP: 40
Level 5 complete. XP: 50
Total XP earned: 50

Change levelsCompleted to 100 and the loop handles it - no code changes needed.

Tracing loop execution

Understanding loops requires tracing through iterations. Let's trace a simpler example:

int health{ 3 };
while (health > 0)
{
    std::cout << "Health: " << health << '\n';
    --health;
}
std::cout << "Game over\n";
Iteration health at start Condition Output
1 3 3 > 0 = true "Health: 3"
2 2 2 > 0 = true "Health: 2"
3 1 1 > 0 = true "Health: 1"
- 0 0 > 0 = false loop ends

After the loop, "Game over" prints.

Loops that never execute

If the condition is false initially, the loop body is skipped entirely:

int attempts{ 0 };
while (attempts > 0)
{
    std::cout << "Trying...\n";
    --attempts;
}
std::cout << "Done\n";

Since 0 > 0 is false, this prints only "Done".

This behavior is useful - you don't need special handling for empty cases:

int itemsToProcess{ getUserInput() };  // might be 0
while (itemsToProcess > 0)
{
    processItem();
    --itemsToProcess;
}
// Works correctly even if user entered 0

Infinite loops

If the condition never becomes false, the loop runs forever:

int fuel{ 100 };
while (fuel > 0)  // condition never changes!
{
    std::cout << "Flying...\n";
    // forgot to decrease fuel
}

Without --fuel;, fuel stays 100 and 100 > 0 is always true. The program prints "Flying..." until you force-quit it.

Intentional infinite loops

Sometimes you want an infinite loop. Use while (true):

#include <iostream>

int main()
{
    int balance{ 1000 };

    while (true)
    {
        std::cout << "Balance: $" << balance << '\n';
        std::cout << "Withdraw amount (0 to exit): ";

        int amount{};
        std::cin >> amount;

        if (amount == 0)
            break;  // exit the loop

        if (amount > balance)
        {
            std::cout << "Insufficient funds\n";
            continue;  // skip to next iteration
        }

        balance -= amount;
    }

    std::cout << "Thank you for banking with us.\n";

    return 0;
}

This ATM simulation runs until the user enters 0. The break statement exits the loop, and continue skips to the next iteration. We'll cover these in detail in a later lesson.

Best Practice
Use `while (true)` for intentional infinite loops. It clearly signals that the loop is meant to run indefinitely until explicitly exited.

The semicolon trap

A semicolon after the while condition creates a subtle bug:

int timer{ 5 };
while (timer > 0);  // semicolon creates an empty loop body!
{
    std::cout << timer << "...\n";
    --timer;
}

This is actually:

int timer{ 5 };
while (timer > 0)
    ;  // empty statement - does nothing, forever

{
    // this block is not part of the loop
    std::cout << timer << "...\n";
    --timer;
}

Since timer never changes inside the loop, 5 > 0 is always true and the program hangs.

Warning
Never put a semicolon directly after a while condition unless you're intentionally creating a single-statement loop body.

Why loop variables should be signed

Consider counting down with an unsigned variable:

unsigned int missiles{ 3 };

while (missiles >= 0)  // always true for unsigned!
{
    std::cout << "Missiles: " << missiles << '\n';
    --missiles;
}

This looks reasonable but loops forever. After printing 3, 2, 1, 0, decrementing the unsigned value 0 wraps to 4294967295. Since unsigned values can never be negative, missiles >= 0 is always true.

Best Practice
Use signed integers for loop variables. Unsigned types can cause subtle infinite loops when decrementing.

Doing something periodically

Use the remainder operator to take action every Nth iteration:

#include <iostream>

int main()
{
    int second{ 0 };
    while (second <= 30)
    {
        std::cout << second << "s ";

        if (second % 5 == 0)
            std::cout << "[checkpoint] ";

        if (second % 10 == 0)
            std::cout << '\n';

        ++second;
    }

    return 0;
}

Output:

0s [checkpoint]
1s 2s 3s 4s 5s [checkpoint] 6s 7s 8s 9s 10s [checkpoint]
11s 12s 13s 14s 15s [checkpoint] 16s 17s 18s 19s 20s [checkpoint]
21s 22s 23s 24s 25s [checkpoint] 26s 27s 28s 29s 30s [checkpoint]

Checkpoints appear at 0, 5, 10, 15, 20, 25, 30 (every 5 seconds). Newlines appear at 0, 10, 20, 30 (every 10 seconds).

Nested loops

Loops can contain other loops. The inner loop runs completely for each iteration of the outer loop.

#include <iostream>

int main()
{
    int rows{ 4 };
    int cols{ 6 };

    int y{ 0 };
    while (y < rows)
    {
        int x{ 0 };
        while (x < cols)
        {
            std::cout << '*';
            ++x;
        }
        std::cout << '\n';
        ++y;
    }

    return 0;
}

Output:

******
******
******
******

For each row (y = 0, 1, 2, 3), the inner loop prints 6 asterisks (x = 0, 1, 2, 3, 4, 5), then we print a newline and move to the next row.

Here's a more interesting pattern:

#include <iostream>

int main()
{
    int size{ 5 };

    int row{ 1 };
    while (row <= size)
    {
        int star{ 1 };
        while (star <= row)
        {
            std::cout << '*';
            ++star;
        }
        std::cout << '\n';
        ++row;
    }

    return 0;
}

Output:

*
**
***
****
*****

The inner loop's limit (row) changes with each outer iteration, creating the triangle pattern.

Summary

  • Loops execute code repeatedly until a condition becomes false
  • While loops check the condition before each iteration - if initially false, the body never executes
  • Infinite loops occur when the condition never becomes false - intentional ones use while (true)
  • The semicolon trap: a semicolon after while (condition) creates an empty loop body
  • Signed loop variables prevent wrap-around bugs that cause infinite loops with unsigned types
  • The remainder operator (%) enables periodic actions every Nth iteration
  • Nested loops place one loop inside another - the inner loop completes fully for each outer iteration

While loops provide fundamental repetition capabilities. The for loop (next lesson) offers more convenient syntax for counting patterns.