Course outline · 0% complete

0/27 lessons0%

Course overview →

Loops: while and for

lesson 3-3 · ~13 min · 8/27

Repeating work

Loops are where programs spend nearly all of their running time, so they are where speed is won or lost. Every algorithms problem you will ever solve reads its input in one. C++ has Python's while, plus a counted for you will type thousands of times in your career.

while

int n = 3;
while (n > 0) {
    std::cout << n << "\n";
    n--;              // subtract 1, from lesson 2-2
}

This is exactly Python's while, with parentheses around the condition and braces around the body.

The classic counted for loop

Python's for i in range(5) becomes:

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

The three parts inside the parentheses are separated by semicolons:

  1. int i = 0 runs once, before the loop starts.
  2. i < 5 is checked before every iteration, and false means stop.
  3. i++ runs after every iteration.

break and continue work exactly as in Python. The loop variable i exists only inside the loop, which is the scoping you want, since a counter has no meaning once the counting is done.

Summing 1 through 10

The counted loop with an accumulator, which is the most common shape a for loop takes.

#include <iostream>

int main() {
    int sum = 0;
    for (int i = 1; i <= 10; i++) {
        sum += i;
    }
    std::cout << "sum = " << sum << "\n";
    return 0;
}

Output

sum = 55

Two choices in the header work together. Starting at i = 1 and testing i <= 10 covers exactly the numbers 1 through 10, where the inclusive <= is what includes 10 itself.

sum is declared before the loop, and that placement is essential. A declaration inside the body would create a fresh sum on every iteration, initialized to 0 each time, and the total would come out as 10. The counter i goes the other way, declared inside the header so that it stops existing when the loop ends, which is the scoping you want for a variable with no meaning outside the loop.

Counting the iterations

The body of for (int i = 0; i < n; i++) runs exactly n times when n is 4, with i taking the values 0, 1, 2, and 3. The check fails when i reaches 4, before the body runs a fifth time.

Starting at 0 with a strict < bound is what produces that clean count, matching Python's range(n). The same header with i <= n would run five times, which is the off-by-one error this idiom exists to avoid.

That 0-to-n-1 range is also exactly how arrays and vectors are indexed, as unit 5 and unit 8 will show, so the loop and the data line up without any arithmetic in the middle. That alignment is the real reason to make this form your default rather than counting from 1.

Nested loops

A loop inside a loop runs the inner body once for every combination. The outer loop picks a row, and the inner loop runs to completion for that row. This is the shape of grid traversal, of comparing all pairs, and of most pattern-printing warm-ups:

for (int row = 1; row <= 4; row++) {
    for (int col = 1; col <= row; col++) {
        std::cout << "*";
    }
    std::cout << "\n";   // end the row
}

The inner bound is col <= row, so row 1 prints one star, row 2 prints two, and so on down to four. The total work is 1 + 2 + 3 + 4 = 10 stars.

Nested loops multiply work rather than adding it. With both bounds at n, the body runs n × n times, so doubling n quadruples the work. That fact becomes central in unit 8, where algorithm cost gets measured properly, and it is the reason a nested loop over a large input is the first thing to look at when a program is too slow.

A triangle that shrinks

The inner bound decides the shape. Here it is col <= 5 - row, so the widest row comes first and each row after it is one star shorter.

#include <iostream>

int main() {
    for (int row = 1; row <= 4; row++) {
        for (int col = 1; col <= 5 - row; col++) {
            std::cout << "*";
        }
        std::cout << "\n";
    }
    return 0;
}

Output

*
**
***
****

The inner loop's bound is an expression rather than a constant, which is what ties the two loops together. With row at 1 the bound is 4, and with row at 4 the bound is 1, so the star count runs 4, 3, 2, 1 down the rows.

The newline is printed by the outer loop, after the inner loop has finished. That placement is what ends each row, and moving it inside the inner loop would put every single star on its own line.

Reading a known count of inputs

A pattern you will use in nearly every DSA problem: the first input says how many values follow, then you loop and read them.

int n;
std::cin >> n;          // how many numbers?
for (int i = 0; i < n; i++) {
    int x;
    std::cin >> x;      // read the next one
    // ... process x ...
}

Because >> skips all whitespace (lesson 1-2), it does not matter whether the numbers arrive on one line or many.

Reading n values and tracking the maximum

The count-then-values input pattern combined with the biggest-so-far search. The first value read becomes the starting champion, and the loop compares the remaining values against it.

#include <iostream>

int main() {
    int n;
    std::cin >> n;

    int mx;
    std::cin >> mx;

    for (int i = 1; i < n; i++) {
        int x;
        std::cin >> x;
        if (x > mx) {
            mx = x;
        }
    }

    std::cout << "max = " << mx << "\n";
    return 0;
}

Input

5
3 9 2 9 4

Output

max = 9

The loop starts at i = 1 rather than 0, because the first of the five values was already consumed into mx before the loop began. Reading it separately is what avoids the need for a sentinel starting value, and it is the correct approach in general, since no constant is guaranteed to be smaller than every possible input.

The duplicate 9 in the input is deliberate. The comparison is a strict x > mx, so the second 9 does not replace the first, and the answer is unaffected either way. Using >= would produce the same number here and is worth avoiding anyway, since it does pointless assignments and would give a different answer to a question like which position the maximum was found at.