Skip to content

Loops

You’ve already learned how to express sequential logic in C++ (a series of operations written as a sequence of lines of code that execute from top to bottom) and conditional logic (using if, else if, and else statements). Now it’s time to explore another key type of logic in C++: repetition logic.

Repetition logic allows an operation or set of operations to be performed repeatedly as long as a specified condition is met. For example, you might write a program that prints “Hello” ten times, a program that asks the user for 100 numbers and calculates their sum, or a terminal-based chess game that continues until there is a win or a draw. These scenarios all involve executing a set of operations repeatedly until a specific condition is satisfied or no longer valid.

In C++, repetition logic is implemented using loops. A loop is a block of code that executes repeatedly as long as a specified boolean expression evaluates to true. C++ provides three types of loops: while loops, do-while loops, and for loops. While any of these loops can theoretically handle any repetition logic problem, each type is often better suited to particular scenarios.

While Loops

The simplest type of loop in C++ is the while loop. It functions similarly to an if statement but with one key difference: after the body of a while loop finishes executing, the control flow returns to the loop’s condition. If the condition is still true, the body will execute again, and this process repeats. When the condition evaluates to false, the body is skipped, and the program continues beyond the loop.

The syntax for a while loop is nearly identical to an if statement, except that while is used instead of if. However, unlike an if statement, a while loop cannot include else if or else branches. The general syntax is:

while (<condition>) {
<loop body>
}

Here, <condition> is a boolean expression that determines whether the loop should continue executing. The <loop body> contains one or more statements to be executed repeatedly.

When a program encounters a while loop, it evaluates the condition. If the condition is false, the loop body is skipped, and the program proceeds with the next statement after the loop. If the condition is true, the loop executes its body. After executing the body, the condition is evaluated again. This process repeats until the condition becomes false.

The condition in a while loop often depends on variables whose values change during the loop’s execution. Without such changes, a condition that evaluates to true will remain true, resulting in an infinite loop. An infinite loop occurs when the condition never becomes false, causing the program to run endlessly. Infinite loops are almost always a bug since most programs are expected to terminate at some point.

Suppose you want to write a program that asks the user for numbers until they choose to stop. After each entry, the program asks: “Would you like to enter another number? Type ‘y' for yes or ‘n' for no.” The program keeps a running sum of the numbers entered and displays the total at the end. Here’s how you could implement this using a while loop:

input_numbers.cpp
#include <iostream>
/*
* Function: prompt_for_number
* Description: Prompts the user to enter a number.
* Returns (double): The number provided by the user.
*/
double prompt_for_number() {
std::cout << "Enter a number: ";
double user_number;
std::cin >> user_number;
return user_number;
}
/*
* Function: prompt_for_go_again
* Description: Prompts the user to decide whether to continue.
* Returns (char): 'y' if the user wants to continue, 'n' otherwise.
*/
char prompt_for_go_again() {
std::cout << "Would you like to enter another number? Type 'y' for yes or 'n' for no: ";
char response;
std::cin >> response;
return response;
}
int main() {
char go_again_response = 'y';
double running_sum = 0.0;
while (go_again_response == 'y') {
double number = prompt_for_number();
running_sum += number;
go_again_response = prompt_for_go_again(); // Update loop condition (Line A)
}
std::cout << "The sum of the numbers you entered is: " << running_sum << std::endl;
}

Here’s an explanation of the program:

  1. Loop Condition: The condition go_again_response == 'y' ensures the loop continues as long as the user wants to input more numbers. This condition is a boolean expression, similar to those used in if statements.
  2. Updating the Condition: The variable go_again_response is updated within the loop body at Line A to reflect the user’s input. This is essential to allow the loop condition to eventually become false, preventing an infinite loop. Without this update, the program would keep running indefinitely, and any code after the loop would be unreachable (dead code).
  3. Initialization: The variable go_again_response is initialized to 'y' before the loop starts. This ensures the loop executes at least once, even before any user input is provided. This “artificial initialization” effectively tricks the loop into running initially. In the next section, you’ll learn about a different type of loop that avoids this kind of setup.

Do-While Loops

Sometimes, you need a loop that always executes at least once. This was the case in the previous example: the user must always enter at least one number, so it’s imperative that the loop executes at least once.

There are two common strategies for ensuring a while loop runs at least once:

  1. Artificial Initialization: Initialize the condition variable to a value that guarantees the loop condition is true on the first evaluation. In the previous example, go_again_response was initialized to 'y' before the loop started.
  2. Duplicate the Loop Body: Replicate the loop’s logic outside the loop to ensure the body runs once before checking the condition.

Both strategies have drawbacks. The first approach often involves extra variables to “trick” the loop into executing. The second violates the DRY (Don’t Repeat Yourself) principle, as it duplicates code unnecessarily. While you could extract the duplicated code into a function, this still results in an unnecessary function call.

Thankfully, C++ provides the do-while loop, a construct specifically designed for cases where a loop must run at least once. The key difference between while and do-while loops lies in their execution order:

  1. While Loop: The condition is evaluated first. If the condition is true, the loop body is executed. This repeats until the condition becomes false.
  2. Do-While Loop: The loop body is executed first. After the body executes, the condition is evaluated. If the condition is true, the body executes again. This continues until the condition becomes false.

This distinction is reflected in their syntax. A while loop places the condition before the loop body, while a do-while loop places the body before the condition:

do {
<loop body>
} while (<condition>);

A semicolon is required after the closing parenthesis of a do-while loop’s condition.

Let’s revisit the previous example, where the program asked the user to input numbers until they chose to stop. Using a do-while loop, we can simplify the code:

input_numbers.cpp
#include <iostream>
/*
* Function: prompt_for_number
* Description: Prompts the user to enter a number.
* Returns (double): The number entered by the user.
*/
double prompt_for_number() {
std::cout << "Enter a number: ";
double user_number;
std::cin >> user_number;
return user_number;
}
/*
* Function: prompt_for_go_again
* Description: Prompts the user to decide whether to continue.
* Returns (char): 'y' to continue, 'n' to stop.
*/
char prompt_for_go_again() {
std::cout << "Would you like to enter another number? Type 'y' for yes or 'n' for no: ";
char response;
std::cin >> response;
return response;
}
int main() {
char go_again_response;
double running_sum = 0.0;
do {
double number = prompt_for_number();
running_sum += number;
go_again_response = prompt_for_go_again(); // Update loop condition
} while (go_again_response == 'y');
std::cout << "The sum of the numbers you entered is: " << running_sum << std::endl;
}

The do-while loop eliminates the need to initialize go_again_response to 'y' before the loop. Since the loop body executes before the condition is evaluated, the user’s response is always captured before the condition is checked. Note that go_again_response is still declared outside the loop, otherwise it won’t be accessible from the condition.

The structure is more intuitive because the logic aligns with the problem’s requirements—a body that executes before the condition is checked.

We can streamline the code by taking advantage of the fact that assignment operations are expressions. Instead of updating go_again_response inside the loop body, we can include the assignment directly in the condition:

do {
double number = prompt_for_number();
running_sum += number;
} while ((go_again_response = prompt_for_go_again()) == 'y');

This approach removes the need to update go_again_response explicitly in the loop body. However, it wouldn’t work in a while loop because the condition is evaluated before the body executes, disrupting the order of prompts.

If the condition variable (go_again_response) is only used within the condition and not elsewhere in the program, it can be removed entirely:

do {
double number = prompt_for_number();
running_sum += number;
} while (prompt_for_go_again() == 'y');

This works because the function prompt_for_go_again() directly provides the value needed for the condition. However, if the variable is required elsewhere, this simplification is not viable.

do-while loops are ideal when you need a loop that always executes at least once. For scenarios where the loop may not execute at all, depending on the initial condition, a while loop is more appropriate.

For Loops

While while and do-while loops are excellent for repetition logic of the form “repeat these operations as long as this condition is satisfied,” they are not ideal for count-based repetition. Count-based repetition follows the pattern “repeat these operations exactly N times,” where N is a value that can be easily determined before the loop starts.

A common solution to count-based repetition involves creating a counter variable (typically of type int) that increments during each iteration of the loop body. The loop condition is built around this counter, ensuring that once the counter reaches a certain value, the loop terminates.

Suppose you want to write a program that prints “Hello, World!” exactly 100 times. This is a classic count-based repetition problem. Using a while loop, the solution might look like this:

#include <iostream>
int main() {
int counter = 0;
while (counter < 100) {
std::cout << "Hello, World!" << std::endl;
counter++;
}
}

Here’s what happens during execution:

  • On the first iteration, counter is 0, and the condition 0 < 100 is true. The program prints “Hello, World!” and increments counter to 1.
  • On the second iteration, counter is 1, and 1 < 100 is true. The program prints “Hello, World!” again and increments counter to 2.
  • This process repeats until counter reaches 100, at which point the condition counter < 100 evaluates to false, and the loop terminates.

In this example, the loop executes exactly 100 times, as intended. Indeed, the total number of values within 0, 1, 2, …, 99 is 100.

While the while loop works for count-based repetition, it has two key drawbacks:

  1. Risk of Errors: In a large loop body, the counter variable’s increment operation (counter++) can be overlooked, duplicated, or omitted entirely. This could result in:
    • A loop that executes too few times.
    • An infinite loop if the counter is never incremented.
  2. Readability: If the counter increment is buried deep in a complex loop body, it can be difficult to quickly determine how many times the loop will execute.

Count-based loops are extremely common, so these issues have influenced the design of virtually all high-level imperative programming languages.

C++ provides a for loop, a specialized construct for count-based repetition. A for loop is designed to consolidate all the logic related to the counter variable into a single location—at the beginning of the loop. This design reduces the risk of errors and improves readability, regardless of the complexity of the loop body.

A for loop in C++ is similar to a while loop but uses a specialized syntax optimized for count-based repetition. Its structure is as follows:

for (<initialization statement>; <condition>; <post-operation>) {
<loop body>
}

This syntax can seem confusing at first, so here’s a detailed breakdown:

  1. The <initialization statement> is executed only once, right before the loop starts. It typically initializes a counter variable, which can also be declared within this statement. For example, rewriting the count-based while loop from earlier as a for loop might use the initialization statement int counter = 0.
  2. The <condition> determines whether the loop will execute. If the condition evaluates to true, the loop body runs; if false, the loop terminates. The condition is evaluated before the loop body runs for the first time, making a for loop more like a while loop than a do-while loop. If the condition is initially false, the loop will not execute at all. In our earlier example, the condition would be counter < 100.
  3. The <post-operation> executes immediately after each iteration of the loop body and before re-evaluating the condition. This is often used to increment/decrement the counter variable. For example, the post-operation might be counter++. Importantly, when using a for loop, ensure the counter increment occurs in the post-operation and not in the loop body to avoid duplication or errors.

Here’s how the count-based while loop example can be rewritten as a for loop:

#include <iostream>
int main() {
for (int counter = 0; counter < 100; counter++) {
std::cout << "Hello, World!" << std::endl;
}
}

One major difference is variable scope. In the for loop example above, the counter variable counter is declared within the initialization statement, meaning its scope is limited to the loop itself. Once the loop finishes, counter is no longer accessible.

For example, the following code will fail to compile because counter is out of scope after the loop:

#include <iostream>
int main() {
for (int counter = 0; counter < 100; counter++) {
std::cout << "Hello, World!" << std::endl;
}
std::cout << "The loop terminated when the counter reached: " << counter << std::endl;
}

To resolve this, declare the counter variable outside the loop if you need to use it afterward:

#include <iostream>
int main() {
int counter;
for (counter = 0; counter < 100; counter++) {
std::cout << "Hello, World!" << std::endl;
}
std::cout << "The loop terminated when the counter reached: " << counter << std::endl;
}

When executed, this program prints “Hello, World!” 100 times, followed by: The loop terminated when the counter reached: 100.

The counter variable can be used within the loop body to modify the operations performed in each iteration. For example, suppose you want to print all integers from 0 to 99, separated by commas. Here’s how you could do it:

print_integers.cpp
#include <iostream>
int main() {
for (int counter = 0; counter < 100; counter++) {
if (counter > 0) {
std::cout << ", ";
}
std::cout << counter;
}
}

This program prints the numbers 0 to 99 in a single line, separated by commas. The if statement ensures that commas are only printed before the second number onward, avoiding a leading comma.

The <initialization statement> and <post-operation> in a for loop can be any valid C++ statements, not just those involving a counter variable. However, the most common pattern for simple count-based loops includes:

  • Initialization: Setting the counter to 0 or N.
  • Condition: Comparing the counter to the target value using a less-than operator < or greater-then operator >.
  • Post-Operation: Incrementing or decrementing the counter by 1 (counter++ or counter--).

This pattern is widely used because of its simplicity, but variations exist depending on the problem. These will be explored in future discussions.

Counter Name

Since the primary purpose of a for loop is to handle count-based problems, almost every for loop declares or initializes a counter variable in its initialization statement. Because this use case is so common, it has become standard practice to name the counter variable i.

In previous examples, we used the name counter to explicitly indicate its role. However, over time, i has become the most widely recognized name for counter variables. It’s so universally understood among programmers that its meaning is immediately clear. While it’s generally discouraged to use single-letter or heavily abbreviated variable names, this is a well-established exception.

The use of i as a counter variable originates from mathematical notation, where i is often used to index elements of a vector or rows of a matrix. In programming, for loops are frequently used for similar tasks, such as iterating over elements in strings, arrays, or other data structures. We’ll explore these applications in future lectures.

Similarly, in nested loops (see below), it is common to use i, j, and k as counter variables for the outer, middle, and inner loops, respectively.

Header Tricks

As we’ve mentioned, the initialization statement and post-operation in a for loop header can be any valid C++ statement. In most examples, we follow a common pattern for simple count-based loops:

  1. Initialize the counter variable to 0.
  2. Use a less-than operator to compare the counter against the target number of iterations.
  3. Increment the counter by 1 in the post-operation.
for (int i = 0; i < n; i++) {
...
}

In this code, n specifies how many times the loop should execute.

While the above pattern is common, it can be unintuitive in some scenarios. Consider a situation where you need to print all integers from 7 to 54, inclusive. This involves printing 48 numbers, but figuring that out isn’t immediately obvious—you might need to calculate it manually. Once you determine that n = 48, you could set up the loop header as follows:

for (int i = 0; i < 48; i++) {
if (i > 0) {
std::cout << ", ";
}
std::cout << (i + 7);
}

While the above solution works, it’s not entirely intuitive. The loop’s purpose is to print numbers from 7 to 54, yet the counter runs from 0 to 47. In such cases, breaking the common pattern often makes the code cleaner. By directly iterating the counter over the desired range, the logic becomes clearer:

for (int i = 7; i <= 54; i++) {
if (i > 7) {
std::cout << ", ";
}
std::cout << i;
}

This approach removes the need for offsetting and directly iterates over the values to be printed, making the code more straightforward and easier to understand.

The flexibility of for loops allows for creative variations. For instance, suppose you want to print numbers backward from 54 to 7, skipping every other number (e.g., 54, 52, 50, …, 8). This requires adjusting the loop header and the comma placement logic:

for (int i = 54; i >= 7; i -= 2) {
if (i < 54) {
std::cout << ", ";
}
std::cout << i;
}

Here, the counter starts at 54, decrements by 2 after each iteration, and stops when it’s less than 7. The if statement ensures commas are placed appropriately.

Break and Continue

In C++, two special keywords, break and continue, are designed for use within loops. While they can technically be used on their own, they are most effective when placed inside an if statement nested within the loop. Here’s what each does:

  • break: Immediately exits the entire loop, skipping any remaining iterations and continuing execution with the code that follows the loop.
  • continue: Immediately ends the current iteration of the loop, skipping the remaining code in the loop body and jumping to the next iteration.

If the loop condition becomes false during the current iteration, continue might behave similarly to break, as it also ends the loop. However, if the loop condition or post-operation has side effects, break and continue will behave differently.

Both statements can simplify handling special cases within loops but should be used sparingly. The primary purpose of choosing among the three types of loops (for, while, do-while) is to clearly define the loop’s logic in the header. Overusing break and continue can obscure the intended behavior of a loop, making the code harder to read and maintain.

This for loop allows the user to make up to 10 guesses for a “magic number” (777). If the user guesses correctly, the break statement exits the loop early:

guess_number_for.cpp
for (int i = 10; i > 0; i--) {
std::cout << "You have " << i << " guesses left." << std::endl;
std::cout << "Guess the magic number: ";
int guess;
std::cin >> guess;
if (guess == 777) {
std::cout << "You got it!" << std::endl;
// Exit the loop immediately since the user guessed correctly.
break;
}
}

If break was used outside the if statement, it would execute unconditionally, causing the loop to terminate after a single iteration. This would defeat the purpose of the loop, making subsequent code within the loop unreachable (dead code).

In the next example, this while loop asks the user for 10 positive integers and calculates their sum. If the user enters a non-positive integer, the continue statement skips the current iteration, prompting the user again without adding the invalid input to the sum or incrementing the counter.

while_continue_integers.cpp
int num_positive_values_entered = 0;
int sum_of_positive_values = 0;
while (num_positive_values_entered < 10) {
std::cout << "Enter a positive integer: ";
int value;
std::cin >> value;
if (value <= 0) {
std::cout << "I said to enter a POSITIVE integer!" << std::endl;
// Skip to the next iteration to prompt the user again.
continue;
}
// Add the valid input to the sum and increment the counter.
sum_of_positive_values += value;
num_positive_values_entered++;
}

Nested Loops

Loops in C++ can be nested, meaning one loop can exist inside another. The outer loop is called the nesting loop, and the inner loop is referred to as the nested loop. Nested loops can add complexity but are useful for solving multidimensional problems or handling repeated tasks within other repeated tasks.

By convention:

  • The outermost loop often uses i as the counter variable name.
  • The next level of nesting typically uses j.
  • If a third nested loop exists (which is rare and often discouraged), k is used.

Although these conventions are widely recognized, deeply nested loops can become hard to read. In such cases, consider breaking some loops into separate functions and using meaningful parameter names to improve clarity.

If a break or continue statement is used inside a nested loop, it only applies to the innermost loop containing the statement. For example:

  • If a break is executed in a for loop nested inside a while loop, only the for loop will terminate.
  • The outer while loop will continue running, potentially restarting the for loop in the next iteration.

How many times do you think the following program will print “Hello”?

Nested Loops Example 1
// nested_loop_1.cpp
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 5; j++) {
std::cout << "Hello" << std::endl;
}
}
  • The outer loop (i) runs 10 iterations.
  • For each iteration of the outer loop, the inner loop (j) runs 5 iterations.
  • During each iteration of the inner loop, “Hello” is printed.

Hence, “Hello” is printed 5×10=505 \times 10 = 50 .

Here’s a slightly harder example. How many times will the following program print “Hello”, and how many times will it print “Goodbye”?

Nested Loops Example 2
// nested_loop_2.cpp
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 5; j++) {
std::cout << "Hello" << std::endl;
}
std::cout << "Goodbye" << std::endl;
}
  • The outer loop (i) runs 10 iterations.
  • The inner loop (j) runs 5 iterations for each iteration of the outer loop.
  • After the inner loop finishes, “Goodbye” is printed once.

Hence, “Hello” is printed 5×10=505 \times 10 = 50 times and “Goodbye” is printed once per outer loop iteration, for a total of 10 times.

Here’s an even harder example, still using nested for loops:

Variable-Length Inner Loop
// nested_loop_variable.cpp
for (int i = 1; i <= 9; i++) {
for (int j = 0; j < i; j++) {
std::cout << "Hello" << std::endl;
}
}
  • The outer loop (i) runs 9 iterations, with i taking values from 1 to 9.
  • The inner loop (j) runs i iterations for each iteration of the outer loop. For example:
    • When i = 1, the inner loop runs 1 iteration.
    • When i = 2, the inner loop runs 2 iterations.
    • When i = 9, the inner loop runs 9 iterations.

Hence, the number of “Hello”s printed is the sum of the integers from 1 to 9: 1+2+3++9=451 + 2 + 3 + \dots + 9 = 45

Let’s do one more nested for loop example. What does the following program do? Note that \t is a “tab” character, so printing it will print a tab indentation to the terminal.

Nested Loops Example 3
// nested_loop_3.cpp
for (int i = 1; i <= 10; i++) {
for (int j = 1; j <= 10; j++) {
std::cout << (i * j) << "\t";
}
std::cout << "\n";
}
  • Outer loop (i): Runs 10 times, iterating from 1 to 10.
  • Inner loop (j): Runs 10 times for each iteration of the outer loop, also iterating from 1 to 10.
  • During each iteration of the inner loop, the product of i and j is printed, followed by a tab \t for alignment.
  • At the end of each outer loop iteration, a newline \n moves to the next row.

This nested loop prints a 10×1010 \times 10 multiplication table:

Terminal window
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100

Finally, let’s do a crude, academic example that pulls a lot of concepts together, including nested loops, break and continue statements, and scope rules. How many times will the following code print “Hello”?

Nested Loops Example 4
// nested_loop_4.cpp
int i = 0;
while (true) {
if (i >= 10) {
break;
}
for (int i = 0; i < 5; i++) {
if (i < 2) {
continue;
}
if (i >= 4) {
break;
}
std::cout << "Hello" << std::endl;
}
i++;
}

This one is tricky. Let’s break it down:

  1. The while loop should run indefinitely because the condition true is always true. However:
    • Runs until i >= 10 because of the break statement.
    • Starts with i = 0 and increments i at the end of each iteration.
    • The while loop executes a total of 10 full iterations (the 11th iteration terminates early due to break).
  2. The for loop initializes a separate counter variable (int i), which shadows the while loop’s i.
    • Runs up to 5 iterations for each while loop iteration.
    • Behavior within the for loop:
      • continue skips the first two iterations (i < 2).
      • break exits the loop on the 5th iteration (i >= 4).
      • Only the 3rd and 4th iterations of the for loop print "Hello".

The int i in the for loop is a separate variable from the i in the while loop. The while loop’s i is unaffected by the for loop’s operations. Once the for loop ends, the while loop’s i is incremented.

To summarize, the while loop executes 10 full iterations, and during each iteration, the for loop prints “Hello” 2 times (on the 3rd and 4th iterations). Hence, "Hello" is printed 10×2=2010 \times 2 = 20 times.