Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Counter-Controlled Loops with For
Iterate a specific number of times with initialization, condition, and increment in one place.
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:
- Init:
int seconds{ 5 }- creates and initializes the loop variable - Condition:
seconds >= 0- checked before each iteration; if false, loop ends - 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
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.
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.
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);
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
Counter-Controlled Loops with For - Quiz
Test your understanding of the lesson.
Practice Exercises
Multiplication Table
Use nested for loops to create a multiplication table.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!