A more structured loop

While loops work well, but counting patterns require three separate pieces: initializing a counter, testing a condition, and updating the counter. Scatter these across your code and bugs creep in.

The for statement consolidates all three into a single line:

for (init; condition; update)
    statement;

This is equivalent to:

{
    init;
    while (condition)
    {
        statement;
        update;
    }
}

The braces show that variables declared in init exist only within the loop.

Anatomy of a for loop

Consider printing a countdown:

#include <iostream>

int main()
{
    for (int seconds{ 5 }; seconds >= 0; --seconds)
        std::cout << seconds << "... ";

    std::cout << "Liftoff!\n";

    return 0;
}

Output:

5... 4... 3... 2... 1... 0... Liftoff!

Three things happen:

  1. Init: int seconds{ 5 } - creates and initializes the loop variable
  2. Condition: seconds >= 0 - checked before each iteration; if false, loop ends
  3. Update: --seconds - executed after each iteration

The sequence is: init → condition → body → update → condition → body → update → ...

When seconds becomes -1, the condition seconds >= 0 is false and the loop terminates.

Counting patterns

For loops excel at counting. Here are common patterns:

Counting up from 0:

for (int i{ 0 }; i < 5; ++i)
    std::cout << i << ' ';  // prints: 0 1 2 3 4

Counting up from 1:

for (int i{ 1 }; i <= 5; ++i)
    std::cout << i << ' ';  // prints: 1 2 3 4 5

Counting by twos:

for (int i{ 0 }; i <= 10; i += 2)
    std::cout << i << ' ';  // prints: 0 2 4 6 8 10

Counting down:

for (int i{ 5 }; i > 0; --i)
    std::cout << i << ' ';  // prints: 5 4 3 2 1

A practical example: factorial

The factorial of n (written n!) is the product of all positive integers from 1 to n:

#include <iostream>

int factorial(int n)
{
    int result{ 1 };

    for (int i{ 2 }; i <= n; ++i)
        result *= i;

    return result;
}

int main()
{
    std::cout << "5! = " << factorial(5) << '\n';  // 120
    std::cout << "7! = " << factorial(7) << '\n';  // 5040

    return 0;
}

Starting from 2 (since multiplying by 1 does nothing), we multiply result by each successive integer up to n.

Off-by-one errors

The most common loop bug is iterating one too many or too few times:

// Goal: print 1 through 5
for (int i{ 1 }; i < 5; ++i)    // Bug! Stops at 4
    std::cout << i << ' ';

for (int i{ 1 }; i <= 5; ++i)   // Correct
    std::cout << i << ' ';

The difference between < and <= determines whether the final value is included. When starting from 0, use < n to iterate n times. When starting from 1, use <= n to include n.

Prefer < over !=

Both loops below print 0 through 4:

for (int i{ 0 }; i < 5; ++i)   // Preferred
for (int i{ 0 }; i != 5; ++i)  // Risky

But consider what happens if something modifies i inside the loop:

for (int i{ 0 }; i < 5; ++i)
{
    if (i == 3) i += 2;  // i jumps from 3 to 5
    std::cout << i << ' ';
}
// Prints: 0 1 2 5   (loop stops because 6 < 5 is false)

for (int i{ 0 }; i != 5; ++i)
{
    if (i == 3) i += 2;  // i jumps from 3 to 5
    std::cout << i << ' ';
}
// Infinite loop! i goes 0,1,2,5,6,7... never equals 5
Best Practice
Use `<` or `<=` in for loop conditions, not `!=`. Relational comparisons are more forgiving of unexpected loop variable changes.

Omitting parts

Any part of a for loop can be omitted (but the semicolons must remain):

int i{ 0 };
for ( ; i < 5; ++i)      // init already done
    std::cout << i;

for (int i{ 0 }; i < 5; )  // update done elsewhere
{
    std::cout << i;
    ++i;
}

Omitting everything creates an infinite loop:

for (;;)  // equivalent to while (true)
    doSomething();

Use while (true) instead - it's clearer about intent.

Multiple variables

For loops can manage multiple variables using the comma operator:

#include <iostream>

int main()
{
    for (int left{ 0 }, right{ 10 }; left < right; ++left, --right)
        std::cout << left << " <-> " << right << '\n';

    return 0;
}

Output:

0 <-> 10
1 <-> 9
2 <-> 8
3 <-> 7
4 <-> 6

Both variables are declared in init, and both are modified in update. The loop runs while left < right.

Best Practice
Using multiple variables and the comma operator is acceptable in for loops. This is one of the few cases where these practices are considered good style.

Nested for loops

Loops can be nested. The inner loop completes all its iterations for each iteration of the outer loop:

#include <iostream>

int main()
{
    for (int row{ 1 }; row <= 3; ++row)
    {
        for (int col{ 1 }; col <= 4; ++col)
            std::cout << row << "," << col << "  ";

        std::cout << '\n';
    }

    return 0;
}

Output:

1,1  1,2  1,3  1,4
2,1  2,2  2,3  2,4
3,1  3,2  3,3  3,4

For each row (1, 2, 3), the inner loop runs through all columns (1, 2, 3, 4).

A multiplication table demonstrates nested loops well:

#include <iostream>
#include <iomanip>

int main()
{
    for (int a{ 1 }; a <= 5; ++a)
    {
        for (int b{ 1 }; b <= 5; ++b)
            std::cout << std::setw(3) << (a * b);

        std::cout << '\n';
    }

    return 0;
}

Output:

  1  2  3  4  5
  2  4  6  8 10
  3  6  9 12 15
  4  8 12 16 20
  5 10 15 20 25

Loop variable scope

Declare loop variables inside the for statement when possible:

// Good: i exists only within the loop
for (int i{ 0 }; i < 10; ++i)
    process(i);

// i no longer accessible here - prevents accidental misuse

Some believe declaring variables outside is faster:

int i;  // declared once outside
for (i = 0; i < 10; ++i)
    process(i);

This is a myth. Variable creation costs nothing - only initialization does, and that cost is identical whether using declaration or assignment. Worse, the variable remains accessible after the loop, expanding its scope unnecessarily and potentially preventing optimizations.

Best Practice
Declare loop variables inside the for statement. Smaller scope means clearer code and potentially better optimization.

When to use for vs while

Use a for loop when you have a clear loop variable with known start, end, and increment:

for (int page{ 1 }; page <= totalPages; ++page)
    printPage(page);

Use a while loop when there's no obvious counter or when termination depends on runtime conditions:

while (!file.eof())
    processLine(file);
Best Practice
Prefer for loops when you have an obvious loop variable. Prefer while loops when you don't.

Summary

  • For statements combine initialization, condition, and update into one line
  • Execution order: init → condition → body → update → condition → ...
  • Loop variable scope is limited to the for statement when declared in init
  • Off-by-one errors are common - carefully consider < vs <=
  • Prefer < over != in conditions for safety
  • Omitting parts is possible but while (true) is clearer for infinite loops
  • Multiple variables can be managed using commas in init and update
  • Nested loops run the inner loop completely for each outer iteration
  • Declare loop variables inside the for statement for smallest scope
  • Choose for loops when you have a clear counter; while loops otherwise